refactor: Cleanup git state - commit all staged changes

Major refactoring cleanup:
- Add new controller architecture (class-controller-*.php)
- Add new settings-v2 UI (views/settings-v2/)
- Add new CSS architecture (agentic-sidebar.css, tokens)
- Add esbuild build pipeline (scripts/build.js, package.json)
- Add composer dependencies (vendor/)
- Add frontend src directory (assets/js/src/index.jsx)
- Add documentation files
- Remove old/obsolete files (class-settings.php, old CSS)

This commits all pending changes from previous refactoring efforts.
This commit is contained in:
Dwindi Ramadhana
2026-06-17 05:27:58 +07:00
parent d3f142222c
commit 690991c526
7963 changed files with 941566 additions and 67372 deletions

View File

@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: composer
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10

View File

@@ -0,0 +1,175 @@
on:
push:
branches:
- master
pull_request:
name: Qa workflow
env:
extensions: mbstring, intl, iconv, libxml, dom, json, simplexml, zlib, fileinfo
key: cache-v1 # can be any string, change to clear the extension cache.
defaultPHPVersion: '7.3'
jobs:
phpunit-with-coverage:
runs-on: ubuntu-latest
name: Unit tests
steps:
- uses: actions/checkout@v2
- name: Install graphviz
run: sudo apt-get update && sudo apt-get install -y graphviz
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ env.defaultPHPVersion }}
ini-values: memory_limit=2G, display_errors=On, error_reporting=-1
tools: phive
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: composer-${{ hashFiles('**/composer.lock') }}
restore-keys: composer-
- name: Install Composer dependencies
run: |
composer install --no-progress --prefer-dist --optimize-autoloader
- name: Run PHPUnit
run: php vendor/bin/phpunit
phpunit:
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system:
- ubuntu-latest
php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1']
name: Unit tests for PHP version ${{ matrix.php-versions }} on ${{ matrix.operating-system }}
needs:
- phpunit-with-coverage
steps:
- uses: actions/checkout@v2
- name: Install graphviz
run: sudo apt-get update && sudo apt-get install -y graphviz
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extension-csv: mbstring, simplexml
ini-values: memory_limit=2G, display_errors=On, error_reporting=-1
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: composer-${{ hashFiles('**/composer.lock') }}
restore-keys: composer-
- name: Install Composer dependencies
run: |
composer install --no-progress --prefer-dist --optimize-autoloader
- name: Run PHPUnit
run: php vendor/bin/phpunit
codestyle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Restore/cache vendor folder
uses: actions/cache@v1
with:
path: vendor
key: all-build-${{ hashFiles('**/composer.lock') }}
restore-keys: |
all-build-${{ hashFiles('**/composer.lock') }}
all-build-
- name: Restore/cache tools folder
uses: actions/cache@v1
with:
path: tools
key: all-tools-${{ github.sha }}
restore-keys: |
all-tools-${{ github.sha }}-
all-tools-
- name: Code style check
uses: docker://phpdoc/phpcs-ga:latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: -d memory_limit=1024M
phpstan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ env.defaultPHPVersion }}
ini-values: memory_limit=2G, display_errors=On, error_reporting=-1
tools: pecl
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: composer-${{ hashFiles('**/composer.lock') }}
restore-keys: composer-
- name: Install Composer dependencies
run: |
composer install --no-progress --prefer-dist --optimize-autoloader
- name: PHPStan
uses: phpDocumentor/phpstan-ga@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: analyse src tests --configuration phpstan.neon
psalm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 7.3
ini-values: memory_limit=2G, display_errors=On, error_reporting=-1
tools: pecl, psalm
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: composer-${{ hashFiles('**/composer.lock') }}
restore-keys: composer-
- name: Install Composer dependencies
run: |
composer install --no-progress --prefer-dist --optimize-autoloader
- name: Psalm
run: vendor/bin/psalm.phar --output-format=github

View File

@@ -0,0 +1,18 @@
# IDE Shizzle; it is recommended to use a global .gitignore for this but since this is an OSS project we want to make
# it easy to contribute
.idea
/nbproject/private/
.buildpath
.project
.settings
# Build folder and vendor folder are generated code; no need to version this
build/
vendor/
temp/
tools/
*.phar
# By default the phpunit.xml.dist is provided; you can override this using a local config file
phpunit.xml
composer.lock

View File

@@ -0,0 +1,48 @@
before_commands:
- "composer install --no-dev --prefer-source"
checks:
php:
excluded_dependencies:
- phpstan/phpstan
tools:
external_code_coverage:
enabled: true
timeout: 300
filter:
excluded_paths: ["tests", "vendor"]
php_code_sniffer:
enabled: true
config:
standard: PSR2
filter:
paths: ["src/*", "tests/*"]
excluded_paths: []
php_cpd:
enabled: true
excluded_dirs: ["tests", "vendor"]
php_cs_fixer:
enabled: true
config:
level: all
filter:
paths: ["src/*", "tests/*"]
php_loc:
enabled: true
excluded_dirs: ["tests", "vendor"]
php_mess_detector:
enabled: true
config:
ruleset: phpmd.xml.dist
design_rules: { eval_expression: false }
filter:
paths: ["src/*"]
php_pdepend:
enabled: true
excluded_dirs: ["tests", "vendor"]
php_analyzer:
enabled: true
filter:
paths: ["src/*", "tests/*"]
sensiolabs_security_checker: true

21
vendor/phpdocumentor/graphviz/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Mike van Riel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

35
vendor/phpdocumentor/graphviz/Makefile vendored Normal file
View File

@@ -0,0 +1,35 @@
.PHONY: install-phive
install-phive:
mkdir tools; \
wget -O tools/phive.phar https://phar.io/releases/phive.phar; \
wget -O tools/phive.phar.asc https://phar.io/releases/phive.phar.asc; \
gpg --keyserver pool.sks-keyservers.net --recv-keys 0x9D8A98B29B2D5D79; \
gpg --verify tools/phive.phar.asc tools/phive.phar; \
chmod +x tools/phive.phar
.PHONY: setup
setup: install-phive
docker run -it --rm -v${PWD}:/opt/project -w /opt/project phpdoc/phar-ga:latest php tools/phive.phar install --force-accept-unsigned
.PHONY: phpcbf
phpcbf:
docker run -it --rm -v${CURDIR}:/opt/project -w /opt/project phpdoc/phpcs-ga:latest phpcbf ${ARGS}
.PHONY: phpcs
phpcs:
docker run -it --rm -v${PWD}:/opt/project -w /opt/project phpdoc/phpcs-ga:latest -d memory_limit=1024M -s
.PHONY: phpstan
phpstan:
docker run -it --rm -v${CURDIR}:/opt/project -w /opt/project phpdoc/phpstan-ga:latest analyse src tests --configuration phpstan.neon ${ARGS}
.PHONY: psalm
psalm:
docker run -it --rm -v${CURDIR}:/data -w /data php:7.3 vendor/bin/psalm.phar
.PHONY: test
test:
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:7.3 vendor/bin/phpunit
.PHONY: pre-commit-test
pre-commit-test: phpcs phpstan psalm test

27
vendor/phpdocumentor/graphviz/README.md vendored Normal file
View File

@@ -0,0 +1,27 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Travis Status](https://img.shields.io/travis/phpDocumentor/GraphViz.svg?label=Linux)](https://travis-ci.org/phpDocumentor/GraphViz)
[![Appveyor Status](https://img.shields.io/appveyor/ci/phpDocumentor/GraphViz.svg?label=Windows)](https://ci.appveyor.com/project/phpDocumentor/GraphViz/branch/master)
[![Coveralls Coverage](https://img.shields.io/coveralls/github/phpDocumentor/GraphViz.svg)](https://coveralls.io/github/phpDocumentor/GraphViz?branch=master)
[![Scrutinizer Code Coverage](https://img.shields.io/scrutinizer/coverage/g/phpDocumentor/GraphViz.svg)](https://scrutinizer-ci.com/g/phpDocumentor/GraphViz/?branch=master)
[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/phpDocumentor/GraphViz.svg)](https://scrutinizer-ci.com/g/phpDocumentor/GraphViz/?branch=master)
[![Stable Version](https://img.shields.io/packagist/v/phpDocumentor/GraphViz.svg)](https://packagist.org/packages/phpDocumentor/GraphViz)
[![Unstable Version](https://img.shields.io/packagist/vpre/phpDocumentor/GraphViz.svg)](https://packagist.org/packages/phpDocumentor/GraphViz)
GraphViz
========
GraphViz is a library meant for generating .dot files for GraphViz with a
fluent interface.
### PHPStan extension
This library contains a number of magic methods to set attributes on `Node`, `Graph` and `Edge`
this will result in errors when using the library with checks by PHPStan. For your convenience this
library provides an phpStan extension so your code can be checked correctly by phpstan.
```
includes:
- vendor/phpdocumentor/graphviz/extension.neon
```

View File

@@ -0,0 +1,39 @@
{
"name": "phpdocumentor/graphviz",
"description": "Wrapper for Graphviz",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Mike van Riel",
"email": "mike.vanriel@naenius.com"
}
],
"require": {
"php": "^7.2 || ^8.0"
},
"autoload": {
"psr-4": {
"phpDocumentor\\GraphViz\\": "src/phpDocumentor/GraphViz",
"phpDocumentor\\GraphViz\\PHPStan\\": "./src/phpDocumentor/PHPStan"
}
},
"autoload-dev": {
"psr-4": {
"phpDocumentor\\GraphViz\\Test\\": "./tests/phpDocumentor/GraphViz/Test",
"phpDocumentor\\GraphViz\\PHPStan\\": "./tests/phpDocumentor/PHPStan"
}
},
"require-dev": {
"phpunit/phpunit": "^8.2 || ^9.2",
"mockery/mockery": "^1.2",
"phpstan/phpstan": "^0.12",
"ext-simplexml": "*",
"psalm/phar": "^4.15"
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
}
}

View File

@@ -0,0 +1,9 @@
services:
-
class: phpDocumentor\GraphViz\PHPStan\MethodReflectionExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
-
class: phpDocumentor\GraphViz\PHPStan\GraphNodeReflectionExtension
tags:
- phpstan.broker.propertiesClassReflectionExtension

View File

@@ -0,0 +1,22 @@
<?xml version="1.0"?>
<ruleset name="phpDocumentor">
<description>The coding standard for phpDocumentor.</description>
<file>src</file>
<file>tests</file>
<arg value="p"/>
<rule ref="PSR2" />
<rule ref="Doctrine">
<exclude name="SlevomatCodingStandard.TypeHints.UselessConstantTypeHint.UselessDocComment" />
</rule>
<rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
<exclude-pattern>tests/phpDocumentor/GraphViz/Test/GraphTest\.php</exclude-pattern>
</rule>
<rule ref="Generic.Formatting.SpaceAfterNot">
<properties>
<property name="spacing" value="0" />
</properties>
</rule>
</ruleset>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ruleset
name="ProxyManager rules"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd"
>
<rule ref="rulesets/codesize.xml"/>
<rule ref="rulesets/unusedcode.xml"/>
<rule ref="rulesets/design.xml">
<!-- eval is needed to generate runtime classes -->
<exclude name="EvalExpression"/>
</rule>
<rule ref="rulesets/naming.xml">
<exclude name="LongVariable"/>
</rule>
<rule ref="rulesets/naming.xml/LongVariable">
<properties>
<property name="minimum">40</property>
</properties>
</rule>
</ruleset>

View File

@@ -0,0 +1,12 @@
includes:
- /composer/vendor/phpstan/phpstan-mockery/extension.neon
- ./extension.neon
parameters:
level: max
ignoreErrors:
#We have some runtime protection that needs to be tested. Ignore these errors
- '#Call to an undefined method phpDocumentor\\GraphViz\\Edge::someNonExcistingMethod\(\)\.#'
- '#Call to an undefined method phpDocumentor\\GraphViz\\Graph::MyMethod\(\)\.#'
- '#Call to an undefined method phpDocumentor\\GraphViz\\Graph::getNotExisting\(\)\.#'
- '#Call to an undefined method phpDocumentor\\GraphViz\\Node::someNonExistingMethod\(\)\.#'

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
beStrictAboutOutputDuringTests="true"
beStrictAboutTestsThatDoNotTestAnything="true"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="true"
>
<testsuites>
<testsuite name="Graphviz suite">
<directory>./tests/phpDocumentor/GraphViz</directory>
</testsuite>
<testsuite name="PHPStan suite">
<directory>./tests/phpDocumentor/PHPStan</directory>
</testsuite>
</testsuites>
<logging>
<log type="coverage-clover" target="build/logs/clover.xml" />
<log type="junit" target="build/logs/junit.xml" />
</logging>
<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>

26
vendor/phpdocumentor/graphviz/psalm.xml vendored Normal file
View File

@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<psalm
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorLevel="1"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<LessSpecificReturnType errorLevel="info" />
<MissingDependency errorLevel="info" />
<MixedArgumentTypeCoercion>
<errorLevel type="suppress">
<file name="src/phpDocumentor/GraphViz/Graph.php" />
</errorLevel>
</MixedArgumentTypeCoercion>
</issueHandlers>
</psalm>

View File

@@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz;
use function addslashes;
use function preg_replace;
use function strstr;
/**
* Class representing a single GraphViz attribute.
*
* @link http://phpdoc.org
*/
class Attribute
{
/** @var string The name of this attribute */
protected $key = '';
/** @var string The value of this attribute */
protected $value = '';
/**
* Creating a new attribute.
*
* @param string $key Id for the new attribute.
* @param string $value Value for this attribute,
*/
public function __construct(string $key, string $value)
{
$this->key = $key;
$this->value = $value;
}
/**
* Sets the key for this attribute.
*
* @param string $key The new name of this attribute.
*/
public function setKey(string $key): self
{
$this->key = $key;
return $this;
}
/**
* Returns the name for this attribute.
*/
public function getKey(): string
{
return $this->key;
}
/**
* Sets the value for this attribute.
*
* @param string $value The new value.
*/
public function setValue(string $value): self
{
$this->value = $value;
return $this;
}
/**
* Returns the value for this attribute.
*/
public function getValue(): string
{
return $this->value;
}
/**
* Returns the attribute definition as is requested by GraphViz.
*/
public function __toString(): string
{
$key = $this->getKey();
if ($key === 'url') {
$key = 'URL';
}
$value = $this->getValue();
if ($this->isValueContainingSpecials()) {
$value = '"' . $this->encodeSpecials() . '"';
} elseif (!$this->isValueInHtml()) {
$value = '"' . addslashes($value) . '"';
}
return $key . '=' . $value;
}
/**
* Returns whether the value contains HTML.
*/
public function isValueInHtml(): bool
{
$value = $this->getValue();
return isset($value[0]) && ($value[0] === '<');
}
/**
* Checks whether the value contains any any special characters needing escaping.
*/
public function isValueContainingSpecials(): bool
{
return strstr($this->getValue(), '\\') !== false;
}
/**
* Encode special characters so the escape sequences aren't removed
*
* @see http://www.graphviz.org/doc/info/attrs.html#k:escString
*/
protected function encodeSpecials(): string
{
$value = $this->getValue();
$regex = '(\'|"|\\x00|\\\\(?![\\\\NGETHLnlr]))';
return (string) preg_replace($regex, '\\\\$0', $value);
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz;
use function sprintf;
class AttributeNotFound extends Exception
{
public function __construct(string $name)
{
parent::__construct(sprintf('Attribute with name "%s" was not found.', $name));
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz;
use function array_key_exists;
trait Attributes
{
/** @var Attribute[] */
protected $attributes = [];
public function setAttribute(string $name, string $value): self
{
$this->attributes[$name] = new Attribute($name, $value);
return $this;
}
/**
* @throws AttributeNotFound
*/
public function getAttribute(string $name): Attribute
{
if (!array_key_exists($name, $this->attributes)) {
throw new AttributeNotFound($name);
}
return $this->attributes[$name];
}
}

View File

@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz;
use function addslashes;
use function implode;
use function strtolower;
use function substr;
/**
* Class representing an edge (arrow, line).
*
* @link http://phpdoc.org
*/
class Edge
{
use Attributes;
/** @var Node Node from where to link */
private $from;
/** @var Node Node where to to link */
private $to;
/**
* Creates a new Edge / Link between the given nodes.
*
* @param Node $from Starting node to create an Edge from.
* @param Node $to Destination node where to create and
* edge to.
*/
public function __construct(Node $from, Node $to)
{
$this->from = $from;
$this->to = $to;
}
/**
* Factory method used to assist with fluent interface handling.
*
* See the examples for more details.
*
* @param Node $from Starting node to create an Edge from.
* @param Node $to Destination node where to create and
* edge to.
*/
public static function create(Node $from, Node $to): self
{
return new self($from, $to);
}
/**
* Returns the source Node for this Edge.
*/
public function getFrom(): Node
{
return $this->from;
}
/**
* Returns the destination Node for this Edge.
*/
public function getTo(): Node
{
return $this->to;
}
/**
* Magic method to provide a getter/setter to add attributes on the edge.
*
* Using this method we make sure that we support any attribute without too
* much hassle. If the name for this method does not start with get or set
* we return null.
*
* Set methods return this graph (fluent interface) whilst get methods
* return the attribute value.
*
* @param string $name name of the invoked method, expect it to be
* setX or getX.
* @param mixed[] $arguments Arguments for the setter, only 1 is expected: value
*
* @return Attribute|Edge|null
*
* @throws AttributeNotFound
*/
public function __call(string $name, array $arguments)
{
$key = strtolower(substr($name, 3));
if (strtolower(substr($name, 0, 3)) === 'set') {
return $this->setAttribute($key, (string) $arguments[0]);
}
if (strtolower(substr($name, 0, 3)) === 'get') {
return $this->getAttribute($key);
}
return null;
}
/**
* Returns the edge definition as is requested by GraphViz.
*/
public function __toString(): string
{
$attributes = [];
foreach ($this->attributes as $value) {
$attributes[] = (string) $value;
}
$attributes = implode("\n", $attributes);
$fromName = addslashes($this->getFrom()->getName());
$toName = addslashes($this->getTo()->getName());
return <<<DOT
"${fromName}" -> "${toName}" [
${attributes}
]
DOT;
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz;
/**
* Class representing an exception in this GraphViz component.
*/
class Exception extends \Exception
{
}

View File

@@ -0,0 +1,397 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz;
use InvalidArgumentException;
use function array_merge;
use function escapeshellarg;
use function exec;
use function file_put_contents;
use function implode;
use function in_array;
use function realpath;
use function strtolower;
use function substr;
use function sys_get_temp_dir;
use function tempnam;
use function unlink;
use const DIRECTORY_SEPARATOR;
use const PHP_EOL;
/**
* Class representing a graph; this may be a main graph but also a subgraph.
*
* In case of a subgraph:
* When the name of the subgraph is prefixed with _cluster_ then the contents
* of this graph will be grouped and a border will be added. Otherwise it is
* used as logical container to place defaults in.
*
* @method Graph setRankSep(string $rankSep)
* @method Graph setCenter(string $center)
* @method Graph setRank(string $rank)
* @method Graph setRankDir(string $rankDir)
* @method Graph setSplines(string $splines)
* @method Graph setConcentrate(string $concentrate)
*/
class Graph
{
use Attributes;
/** @var string Name of this graph */
protected $name = 'G';
/** @var string Type of this graph; may be digraph, graph or subgraph */
protected $type = 'digraph';
/** @var bool If the graph is strict then multiple edges are not allowed between the same pairs of nodes */
protected $strict = false;
/** @var Graph[] A list of subgraphs for this Graph */
protected $graphs = [];
/** @var Node[] A list of nodes for this Graph */
protected $nodes = [];
/** @var Edge[] A list of edges / arrows for this Graph */
protected $edges = [];
/** @var string The path to execute dot from */
protected $path = '';
/**
* Factory method to instantiate a Graph so that you can use fluent coding
* to chain everything.
*
* @param string $name The name for this graph.
* @param bool $directional Whether this is a directed or undirected graph.
*
* @return Graph
*/
public static function create(string $name = 'G', bool $directional = true): self
{
$graph = new self();
$graph
->setName($name)
->setType($directional ? 'digraph' : 'graph');
return $graph;
}
/**
* Sets the path for the execution. Only needed if it is not in the PATH env.
*
* @param string $path The path to execute dot from
*/
public function setPath(string $path): self
{
$realpath = realpath($path);
if ($path && $path === $realpath) {
$this->path = $path . DIRECTORY_SEPARATOR;
}
return $this;
}
/**
* Sets the name for this graph.
*
* If this is a subgraph you can prefix the name with _cluster_ to group all
* contained nodes and add a border.
*
* @param string $name The new name for this graph.
*/
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* Returns the name for this Graph.
*/
public function getName(): string
{
return $this->name;
}
/**
* Sets the type for this graph.
*
* @param string $type Must be either "digraph", "graph" or "subgraph".
*
* @throws InvalidArgumentException If $type is not "digraph", "graph" or
* "subgraph".
*/
public function setType(string $type): self
{
if (!in_array($type, ['digraph', 'graph', 'subgraph'], true)) {
throw new InvalidArgumentException(
'The type for a graph must be either "digraph", "graph" or '
. '"subgraph"'
);
}
$this->type = $type;
return $this;
}
/**
* Returns the type of this Graph.
*/
public function getType(): string
{
return $this->type;
}
/**
* Set if the Graph should be strict. If the graph is strict then
* multiple edges are not allowed between the same pairs of nodes
*/
public function setStrict(bool $isStrict): self
{
$this->strict = $isStrict;
return $this;
}
public function isStrict(): bool
{
return $this->strict;
}
/**
* Magic method to provide a getter/setter to add attributes on the Graph.
*
* Using this method we make sure that we support any attribute without
* too much hassle. If the name for this method does not start with get
* or set we return null.
*
* Set methods return this graph (fluent interface) whilst get methods
* return the attribute value.
*
* @param string $name Name of the method including get/set
* @param mixed[] $arguments The arguments, should be 1: the value
*
* @return Attribute|Graph|null
*
* @throws AttributeNotFound
*/
public function __call(string $name, array $arguments)
{
$key = strtolower(substr($name, 3));
if (strtolower(substr($name, 0, 3)) === 'set') {
return $this->setAttribute($key, (string) $arguments[0]);
}
if (strtolower(substr($name, 0, 3)) === 'get') {
return $this->getAttribute($key);
}
return null;
}
/**
* Adds a subgraph to this graph; automatically changes the type to subgraph.
*
* Please note that an index is maintained using the name of the subgraph.
* Thus if you have 2 subgraphs with the same name that the first will be
* overwritten by the latter.
*
* @see Graph::create()
*
* @param Graph $graph The graph to add onto this graph as
* subgraph.
*/
public function addGraph(self $graph): self
{
$graph->setType('subgraph');
$this->graphs[$graph->getName()] = $graph;
return $this;
}
/**
* Checks whether a graph with a certain name already exists.
*
* @param string $name Name of the graph to find.
*/
public function hasGraph(string $name): bool
{
return isset($this->graphs[$name]);
}
/**
* Returns the subgraph with a given name.
*
* @param string $name Name of the requested graph.
*/
public function getGraph(string $name): self
{
return $this->graphs[$name];
}
/**
* Sets a node in the $nodes array; uses the name of the node as index.
*
* Nodes can be retrieved by retrieving the property with the same name.
* Thus 'node1' can be retrieved by invoking: $graph->node1
*
* @see Node::create()
*
* @param Node $node The node to set onto this Graph.
*/
public function setNode(Node $node): self
{
$this->nodes[$node->getName()] = $node;
return $this;
}
/**
* Finds a node in this graph or any of its subgraphs.
*
* @param string $name Name of the node to find.
*/
public function findNode(string $name): ?Node
{
if (isset($this->nodes[$name])) {
return $this->nodes[$name];
}
foreach ($this->graphs as $graph) {
$node = $graph->findNode($name);
if ($node) {
return $node;
}
}
return null;
}
/**
* Sets a node using a custom name.
*
* @see Graph::setNode()
*
* @param string $name Name of the node.
* @param Node $value Node to set on the given name.
*/
public function __set(string $name, Node $value): void
{
$this->nodes[$name] = $value;
}
/**
* Returns the requested node by its name.
*
* @see Graph::setNode()
*
* @param string $name The name of the node to retrieve.
*/
public function __get(string $name): ?Node
{
return $this->nodes[$name] ?? null;
}
/**
* Links two nodes to eachother and registers the Edge onto this graph.
*
* @see Edge::create()
*
* @param Edge $edge The link between two classes.
*/
public function link(Edge $edge): self
{
$this->edges[] = $edge;
return $this;
}
/**
* Exports this graph to a generated image.
*
* This is the only method that actually requires GraphViz.
*
* @link http://www.graphviz.org/content/output-formats
* @uses GraphViz/dot
*
* @param string $type The type to export to; see the link above for a
* list of supported types.
* @param string $filename The path to write to.
*
* @throws Exception If an error occurred in GraphViz.
*/
public function export(string $type, string $filename): self
{
$type = escapeshellarg($type);
$filename = escapeshellarg($filename);
// write the dot file to a temporary file
$tmpfile = (string) tempnam(sys_get_temp_dir(), 'gvz');
file_put_contents($tmpfile, (string) $this);
// escape the temp file for use as argument
$tmpfileArg = escapeshellarg($tmpfile);
// create the dot output
$output = [];
$code = 0;
exec($this->path . "dot -T${type} -o${filename} < ${tmpfileArg} 2>&1", $output, $code);
unlink($tmpfile);
if ($code !== 0) {
throw new Exception(
'An error occurred while creating the graph; GraphViz returned: '
. implode(PHP_EOL, $output)
);
}
return $this;
}
/**
* Generates a DOT file for use with GraphViz.
*
* GraphViz is not used in this method; it is safe to call it even without
* GraphViz installed.
*/
public function __toString(): string
{
$elements = array_merge(
$this->graphs,
$this->attributes,
$this->edges,
$this->nodes
);
$attributes = [];
foreach ($elements as $value) {
$attributes[] = (string) $value;
}
$attributes = implode(PHP_EOL, $attributes);
$strict = ($this->isStrict() ? 'strict ' : '');
return <<<DOT
{$strict}{$this->getType()} "{$this->getName()}" {
${attributes}
}
DOT;
}
}

View File

@@ -0,0 +1,137 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz;
use function addslashes;
use function implode;
use function strtolower;
use function substr;
/**
* Class representing a node / element in a graph.
*
* @link http://phpdoc.org
*
* @method void setLabel(string $name) Sets the label for this node.
*/
class Node
{
use Attributes;
/** @var string Name for this node */
protected $name = '';
/**
* Creates a new node with name and optional label.
*
* @param string $name Name of the new node.
* @param string|null $label Optional label text.
*/
public function __construct(string $name, ?string $label = null)
{
$this->setName($name);
if ($label === null) {
return;
}
$this->setLabel($label);
}
/**
* Factory method used to assist with fluent interface handling.
*
* See the examples for more details.
*
* @param string $name Name of the new node.
* @param string|null $label Optional label text.
*/
public static function create(string $name, ?string $label = null): self
{
return new self($name, $label);
}
/**
* Sets the name for this node.
*
* Not to confuse with the label.
*
* @param string $name Name for this node.
*/
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* Returns the name for this node.
*/
public function getName(): string
{
return $this->name;
}
/**
* Magic method to provide a getter/setter to add attributes on the Node.
*
* Using this method we make sure that we support any attribute without
* too much hassle. If the name for this method does not start with get or
* set we return null.
*
* Set methods return this graph (fluent interface) whilst get methods
* return the attribute value.
*
* @param string $name Method name; either getX or setX is expected.
* @param mixed[] $arguments List of arguments; only 1 is expected for setX.
*
* @return Attribute|Node|null
*
* @throws AttributeNotFound
*/
public function __call(string $name, array $arguments)
{
$key = strtolower(substr($name, 3));
if (strtolower(substr($name, 0, 3)) === 'set') {
return $this->setAttribute($key, (string) $arguments[0]);
}
if (strtolower(substr($name, 0, 3)) === 'get') {
return $this->getAttribute($key);
}
return null;
}
/**
* Returns the node definition as is requested by GraphViz.
*/
public function __toString(): string
{
$attributes = [];
foreach ($this->attributes as $value) {
$attributes[] = (string) $value;
}
$attributes = implode("\n", $attributes);
$name = addslashes($this->getName());
return <<<DOT
"{$name}" [
${attributes}
]
DOT;
}
}

View File

@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz\PHPStan;
use phpDocumentor\GraphViz\Attribute;
use phpDocumentor\GraphViz\AttributeNotFound;
use PHPStan\Reflection\ClassMemberReflection;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\FunctionVariant;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
final class AttributeGetterMethodReflection implements MethodReflection
{
/** @var ClassReflection */
private $classReflection;
/** @var string */
private $name;
public function __construct(ClassReflection $classReflection, string $name)
{
$this->classReflection = $classReflection;
$this->name = $name;
}
public function getDeclaringClass(): ClassReflection
{
return $this->classReflection;
}
public function isStatic(): bool
{
return false;
}
public function isPrivate(): bool
{
return false;
}
public function isPublic(): bool
{
return true;
}
public function getName(): string
{
return $this->name;
}
public function getPrototype(): ClassMemberReflection
{
return $this;
}
/**
* @return ParametersAcceptor[]
*/
public function getVariants(): array
{
return [
new FunctionVariant(
TemplateTypeMap::createEmpty(),
null,
[],
false,
new ObjectType(Attribute::class)
),
];
}
public function getDocComment(): ?string
{
return null;
}
public function isDeprecated(): TrinaryLogic
{
return TrinaryLogic::createNo();
}
public function getDeprecatedDescription(): ?string
{
return null;
}
public function isFinal(): TrinaryLogic
{
return TrinaryLogic::createNo();
}
public function isInternal(): TrinaryLogic
{
return TrinaryLogic::createNo();
}
public function getThrowType(): ?Type
{
return new ObjectType(AttributeNotFound::class);
}
public function hasSideEffects(): TrinaryLogic
{
return TrinaryLogic::createMaybe();
}
}

View File

@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz\PHPStan;
use phpDocumentor\GraphViz\AttributeNotFound;
use PHPStan\Reflection\ClassMemberReflection;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\FunctionVariant;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\Php\DummyParameter;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
final class AttributeSetterMethodReflection implements MethodReflection
{
/** @var ClassReflection */
private $classReflection;
/** @var string */
private $name;
/** @var Type */
private $attributeType;
public function __construct(ClassReflection $classReflection, string $name, Type $attributeType)
{
$this->classReflection = $classReflection;
$this->name = $name;
$this->attributeType = $attributeType;
}
public function getDeclaringClass(): ClassReflection
{
return $this->classReflection;
}
public function isStatic(): bool
{
return false;
}
public function isPrivate(): bool
{
return false;
}
public function isPublic(): bool
{
return true;
}
public function getName(): string
{
return $this->name;
}
public function getPrototype(): ClassMemberReflection
{
return $this;
}
/**
* @return ParametersAcceptor[]
*/
public function getVariants(): array
{
return [
new FunctionVariant(
TemplateTypeMap::createEmpty(),
TemplateTypeMap::createEmpty(),
[
new DummyParameter('value', $this->attributeType, false, null, false, null),
],
false,
new ObjectType($this->classReflection->getName())
),
];
}
public function getDocComment(): ?string
{
return null;
}
public function isDeprecated(): TrinaryLogic
{
return TrinaryLogic::createNo();
}
public function getDeprecatedDescription(): ?string
{
return null;
}
public function isFinal(): TrinaryLogic
{
return TrinaryLogic::createNo();
}
public function isInternal(): TrinaryLogic
{
return TrinaryLogic::createNo();
}
public function getThrowType(): ?Type
{
return new ObjectType(AttributeNotFound::class);
}
public function hasSideEffects(): TrinaryLogic
{
return TrinaryLogic::createYes();
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz\PHPStan;
use phpDocumentor\GraphViz\Graph;
use phpDocumentor\GraphViz\Node;
use PHPStan\Reflection\Annotations\AnnotationPropertyReflection;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\PropertiesClassReflectionExtension;
use PHPStan\Reflection\PropertyReflection;
use PHPStan\Type\ObjectType;
final class GraphNodeReflectionExtension implements PropertiesClassReflectionExtension
{
public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
{
return $classReflection->getName() === Graph::class;
}
public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection
{
return new AnnotationPropertyReflection(
$classReflection,
new ObjectType(Node::class),
true,
true
);
}
}

View File

@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz\PHPStan;
use InvalidArgumentException;
use phpDocumentor\GraphViz\Edge;
use phpDocumentor\GraphViz\Graph;
use phpDocumentor\GraphViz\Node;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\MethodsClassReflectionExtension;
use PHPStan\Type\BooleanType;
use PHPStan\Type\FloatType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use RuntimeException;
use SimpleXMLElement;
use function array_key_exists;
use function array_map;
use function file_get_contents;
use function in_array;
use function simplexml_load_string;
use function sprintf;
use function str_replace;
use function stripos;
use function strtolower;
use function substr;
final class MethodReflectionExtension implements MethodsClassReflectionExtension
{
private const SUPPORTED_CLASSES = [
Node::class => 'node',
Graph::class => 'graph',
Edge::class => 'edge',
];
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
{
if (!array_key_exists($classReflection->getName(), self::SUPPORTED_CLASSES)) {
return false;
}
$methods = $this->getMethodsFromSpec(self::SUPPORTED_CLASSES[$classReflection->getName()]);
$expectedAttribute = $this->getAttributeFromMethodName($methodName);
return in_array($expectedAttribute, $methods, true);
}
public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
{
if (stripos($methodName, 'get') === 0) {
return new AttributeGetterMethodReflection($classReflection, $methodName);
}
$attributeName = $this->getAttributeFromMethodName($methodName);
return new AttributeSetterMethodReflection(
$classReflection,
$methodName,
$this->getAttributeInputType($attributeName)
);
}
/**
* @return string[]
*/
private function getMethodsFromSpec(string $className): array
{
$simpleXml = $this->getAttributesXmlDoc();
$elements = $simpleXml->xpath(sprintf("xsd:complexType[@name='%s']/xsd:attribute", $className));
if ($elements === false) {
throw new InvalidArgumentException(
sprintf('Class "%s" does not exist in Graphviz spec', $className)
);
}
return array_map(
static function (SimpleXMLElement $attribute): string {
return strtolower((string) $attribute['ref']);
},
$elements
);
}
private function getAttributeInputType(string $ref): Type
{
$simpleXml = $this->getAttributesXmlDoc();
$attributes = $simpleXml->xpath(sprintf("xsd:attribute[@name='%s']", $ref));
if (empty($attributes)) {
return new StringType();
}
$type = $attributes[0]['type'];
$type = str_replace('xsd:', '', (string) $type);
switch ($type) {
case 'boolean':
return new BooleanType();
case 'decimal':
return new FloatType();
case 'string':
default:
return new StringType();
}
}
private function getAttributesXmlDoc(): SimpleXMLElement
{
$fileContent = file_get_contents(__DIR__ . '/assets/attributes.xml');
if ($fileContent === false) {
throw new RuntimeException('Cannot read attributes spec');
}
$xml = simplexml_load_string($fileContent);
if ($xml === false) {
throw new RuntimeException('Cannot read attributes spec');
}
return $xml;
}
private function getAttributeFromMethodName(string $methodName): string
{
return strtolower(substr($methodName, 3));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,201 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz\Test;
use phpDocumentor\GraphViz\Attribute;
use PHPUnit\Framework\TestCase;
/**
* Test for the the class representing a GraphViz attribute.
*/
class AttributeTest extends TestCase
{
/** @var Attribute */
protected $fixture = null;
/**
* Initializes the fixture for this test.
*/
protected function setUp(): void
{
$this->fixture = new Attribute('a', '1');
}
/**
* Tests the construct method
*
* @covers \phpDocumentor\GraphViz\Attribute::__construct
* @returnn void
*/
public function testConstruct(): void
{
$fixture = new Attribute('MyKey', 'MyValue');
$this->assertInstanceOf(
Attribute::class,
$fixture
);
$this->assertSame('MyKey', $fixture->getKey());
$this->assertSame('MyValue', $fixture->getValue());
}
/**
* Tests the getting and setting of the key.
*
* @covers \phpDocumentor\GraphViz\Attribute::getKey
* @covers \phpDocumentor\GraphViz\Attribute::setKey
*/
public function testKey(): void
{
$this->assertSame(
$this->fixture->getKey(),
'a',
'Expecting the key to match the initial state'
);
$this->assertSame(
$this->fixture,
$this->fixture->setKey('b'),
'Expecting a fluent interface'
);
$this->assertSame(
$this->fixture->getKey(),
'b',
'Expecting the key to contain the new value'
);
}
/**
* Tests the getting and setting of the value.
*
* @covers \phpDocumentor\GraphViz\Attribute::getValue
* @covers \phpDocumentor\GraphViz\Attribute::setValue
*/
public function testValue(): void
{
$this->assertSame(
$this->fixture->getValue(),
'1',
'Expecting the value to match the initial state'
);
$this->assertSame(
$this->fixture,
$this->fixture->setValue('2'),
'Expecting a fluent interface'
);
$this->assertSame(
$this->fixture->getValue(),
'2',
'Expecting the value to contain the new value'
);
}
/**
* Tests whether a string starting with a < is recognized as HTML.
*
* @covers \phpDocumentor\GraphViz\Attribute::isValueInHtml
*/
public function testIsValueInHtml(): void
{
$this->fixture->setValue('a');
$this->assertFalse(
$this->fixture->isValueInHtml(),
'Expected value to not be a HTML code'
);
$this->fixture->setValue('<a>test</a>');
$this->assertTrue(
$this->fixture->isValueInHtml(),
'Expected value to be recognized as a HTML code'
);
}
/**
* Tests whether the toString provides a valid GraphViz attribute string.
*
* @covers \phpDocumentor\GraphViz\Attribute::__toString
*/
public function testToString(): void
{
$this->fixture = new Attribute('a', 'b');
$this->assertSame(
'a="b"',
(string) $this->fixture,
'Strings should be surrounded with quotes'
);
$this->fixture->setValue('a"a');
$this->assertSame(
'a="a\"a"',
(string) $this->fixture,
'Strings should be surrounded with quotes'
);
$this->fixture->setKey('url');
$this->assertSame(
'URL="a\"a"',
(string) $this->fixture,
'The key named URL must be uppercased'
);
$this->fixture->setValue('<a>test</a>');
$this->assertSame(
'URL=<a>test</a>',
(string) $this->fixture,
'HTML strings should not be surrounded with quotes'
);
}
/**
* Tests whether the toString provides a valid GraphViz attribute string.
*
* @covers \phpDocumentor\GraphViz\Attribute::__toString
* @covers \phpDocumentor\GraphViz\Attribute::encodeSpecials
*/
public function testToStringWithSpecials(): void
{
$this->fixture = new Attribute('a', 'b');
$this->fixture->setValue('a\la');
$this->assertSame(
'a="a\la"',
(string) $this->fixture,
'Specials should not be escaped'
);
$this->fixture->setValue('a\l"a');
$this->assertSame(
'a="a\l\"a"',
(string) $this->fixture,
'Specials should not be escaped, but quotes should'
);
$this->fixture->setValue('a\\\\l"a');
$this->assertSame(
'a="a\\\\l\"a"',
(string) $this->fixture,
'Double backslashes should stay the same'
);
}
/**
* Tests whether the isValueContainingSpecials function
*
* @covers \phpDocumentor\GraphViz\Attribute::isValueContainingSpecials
*/
public function testIsValueContainingSpecials(): void
{
$this->fixture->setValue('+ name : string\l+ home_country : string\l');
$this->assertTrue($this->fixture->isValueContainingSpecials());
$this->fixture->setValue('+ ship(): boolean');
$this->assertFalse($this->fixture->isValueContainingSpecials());
}
}

View File

@@ -0,0 +1,153 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz\Test;
use Mockery as m;
use phpDocumentor\GraphViz\AttributeNotFound;
use phpDocumentor\GraphViz\Edge;
use phpDocumentor\GraphViz\Node;
use PHPUnit\Framework\TestCase;
/**
* Test for the the class representing a GraphViz edge (vertex).
*/
class EdgeTest extends TestCase
{
/**
* Tears down the fixture, for example, closes a network connection.
* This method is called after a test is executed.
*/
protected function tearDown(): void
{
m::close();
}
/**
* Tests the construct method
*
* @covers \phpDocumentor\GraphViz\Edge::__construct
*/
public function testConstruct(): void
{
$fromNode = m::mock(Node::class);
$toNode = m::mock(Node::class);
$fixture = new Edge($fromNode, $toNode);
$this->assertInstanceOf(
Edge::class,
$fixture
);
$this->assertSame(
$fromNode,
$fixture->getFrom()
);
$this->assertSame(
$toNode,
$fixture->getTo()
);
}
/**
* Tests the create method
*
* @covers \phpDocumentor\GraphViz\Edge::create
*/
public function testCreate(): void
{
$this->assertInstanceOf(
Edge::class,
Edge::create(new Node('from'), new Node('to'))
);
}
/**
* Tests whether the getFrom method returns the same node as passed
* in the create method
*
* @covers \phpDocumentor\GraphViz\Edge::getFrom
*/
public function testGetFrom(): void
{
$from = new Node('from');
$edge = Edge::create($from, new Node('to'));
$this->assertSame($from, $edge->getFrom());
}
/**
* Tests the getTo method returns the same node as passed
* in the create method
*
* @covers \phpDocumentor\GraphViz\Edge::getTo
*/
public function testGetTo(): void
{
$to = new Node('to');
$edge = Edge::create(new Node('from'), $to);
$this->assertSame($to, $edge->getTo());
}
/**
* Tests the magic __call method, to work as described, return the object
* instance for a setX method, return the value for an getX method, and null
* for the remaining method calls
*
* @covers \phpDocumentor\GraphViz\Edge::__call
* @covers \phpDocumentor\GraphViz\Edge::setAttribute
* @covers \phpDocumentor\GraphViz\Edge::getAttribute
*/
public function testCall(): void
{
$label = 'my label';
$fixture = new Edge(new Node('from'), new Node('to'));
$this->assertInstanceOf(Edge::class, $fixture->setLabel($label));
$this->assertSame($label, $fixture->getLabel()->getValue());
$this->assertNull($fixture->someNonExcistingMethod());
}
/**
* @covers \phpDocumentor\GraphViz\Edge::getAttribute
* @covers \phpDocumentor\GraphViz\AttributeNotFound::__construct
*/
public function testGetNonExistingAttributeThrowsAttributeNotFound(): void
{
$fixture = new Edge(new Node('from'), new Node('to'));
$this->expectException(AttributeNotFound::class);
$this->expectExceptionMessage('Attribute with name "label" was not found');
$fixture->getLabel();
}
/**
* Tests whether the magic __toString method returns a well formatted string
* as specified in the DOT standard
*
* @covers \phpDocumentor\GraphViz\Edge::__toString
*/
public function testToString(): void
{
$fixture = new Edge(new Node('from'), new Node('to'));
$fixture->setLabel('MyLabel');
$fixture->setWeight(45);
$dot = <<<DOT
"from" -> "to" [
label="MyLabel"
weight="45"
]
DOT;
$this->assertSame($dot, (string) $fixture);
}
}

View File

@@ -0,0 +1,430 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz\Test;
use InvalidArgumentException;
use Mockery as m;
use phpDocumentor\GraphViz\AttributeNotFound;
use phpDocumentor\GraphViz\Edge;
use phpDocumentor\GraphViz\Exception;
use phpDocumentor\GraphViz\Graph;
use phpDocumentor\GraphViz\Node;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use function is_readable;
use function preg_replace;
use function sys_get_temp_dir;
use function tempnam;
use const PHP_EOL;
/**
* Test for the the class representing a GraphViz graph.
*/
class GraphTest extends TestCase
{
/** @var Graph */
protected $fixture;
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp(): void
{
$this->fixture = new Graph();
}
/**
* Tears down the fixture, for example, closes a network connection.
* This method is called after a test is executed.
*/
protected function tearDown(): void
{
m::close();
}
/**
* @covers \phpDocumentor\GraphViz\Graph::create
*/
public function testCreate(): void
{
$fixture = Graph::create();
$this->assertInstanceOf(
Graph::class,
$fixture
);
$this->assertSame(
'G',
$fixture->getName()
);
$this->assertSame(
'digraph',
$fixture->getType()
);
$fixture = Graph::create('MyName', false);
$this->assertSame(
'MyName',
$fixture->getName()
);
$this->assertSame(
'graph',
$fixture->getType()
);
}
/**
* @covers \phpDocumentor\GraphViz\Graph::setName
*/
public function testSetName(): void
{
$this->assertSame(
$this->fixture,
$this->fixture->setName('otherName'),
'Expecting a fluent interface'
);
}
/**
* @covers \phpDocumentor\GraphViz\Graph::getName
*/
public function testGetName(): void
{
$this->assertSame(
$this->fixture->getName(),
'G',
'Expecting the name to match the initial state'
);
$this->fixture->setName('otherName');
$this->assertSame(
$this->fixture->getName(),
'otherName',
'Expecting the name to contain the new value'
);
}
/**
* @covers \phpDocumentor\GraphViz\Graph::setType
*/
public function testSetType(): void
{
$this->assertSame(
$this->fixture,
$this->fixture->setType('digraph'),
'Expecting a fluent interface'
);
$this->assertSame(
$this->fixture,
$this->fixture->setType('graph'),
'Expecting a fluent interface'
);
$this->assertSame(
$this->fixture,
$this->fixture->setType('subgraph'),
'Expecting a fluent interface'
);
}
/**
* @covers \phpDocumentor\GraphViz\Graph::setType
*/
public function testSetTypeException(): void
{
$this->expectException(InvalidArgumentException::class);
$this->fixture->setType('fakegraphg');
}
/**
* @covers \phpDocumentor\GraphViz\Graph::getType
*/
public function testGetType(): void
{
$this->assertSame(
$this->fixture->getType(),
'digraph'
);
$this->fixture->setType('graph');
$this->assertSame(
$this->fixture->getType(),
'graph'
);
}
public function testSetStrict(): void
{
$this->assertSame(
$this->fixture,
$this->fixture->setStrict(true),
'Expecting a fluent interface'
);
$this->assertSame(
$this->fixture,
$this->fixture->setStrict(false),
'Expecting a fluent interface'
);
}
public function testIsStrict(): void
{
$this->assertSame(
$this->fixture->isStrict(),
false
);
$this->fixture->setStrict(true);
$this->assertSame(
$this->fixture->isStrict(),
true
);
}
public function testSetPath(): void
{
$this->assertSame(
$this->fixture,
$this->fixture->setPath(__DIR__),
'Expecting a fluent interface'
);
}
/**
* @covers \phpDocumentor\GraphViz\Graph::__call
* @covers \phpDocumentor\GraphViz\Graph::getAttribute
* @covers \phpDocumentor\GraphViz\Graph::setAttribute
*/
public function test__call(): void
{
$this->assertNull($this->fixture->MyMethod());
$this->assertSame($this->fixture, $this->fixture->setBgColor('black'));
$this->assertSame('black', $this->fixture->getBgColor()->getValue());
}
/**
* @covers \phpDocumentor\GraphViz\Graph::getAttribute
* @covers \phpDocumentor\GraphViz\AttributeNotFound::__construct
*/
public function testGetNonExistingAttributeThrowsAttributeNotFound(): void
{
$this->expectException(AttributeNotFound::class);
$this->expectExceptionMessage('Attribute with name "notexisting" was not found');
$this->fixture->getNotExisting();
}
/**
* @covers \phpDocumentor\GraphViz\Graph::addGraph
*/
public function testAddGraph(): void
{
$mock = m::mock(Graph::class);
$mock->expects('setType');
$mock->expects('getName');
$this->assertSame(
$this->fixture,
$this->fixture->addGraph($mock)
);
}
/**
* @covers \phpDocumentor\GraphViz\Graph::hasGraph
*/
public function testHasGraph(): void
{
$mock = m::mock(Graph::class);
$mock->expects('getName')->andReturn('MyName');
$mock->expects('setType');
$this->assertFalse($this->fixture->hasGraph('MyName'));
$this->fixture->addGraph($mock);
$this->assertTrue($this->fixture->hasGraph('MyName'));
}
/**
* @covers \phpDocumentor\GraphViz\Graph::getGraph
*/
public function testGetGraph(): void
{
$mock = m::mock(Graph::class);
$mock->expects('setType');
$mock->expects('getName')->andReturn('MyName');
$this->fixture->addGraph($mock);
$this->assertSame(
$mock,
$this->fixture->getGraph('MyName')
);
}
/**
* @covers \phpDocumentor\GraphViz\Graph::setNode
*/
public function testSetNode(): void
{
$mock = m::mock(Node::class);
$mock->expects('getName')->andReturn('MyName');
$this->assertSame(
$this->fixture,
$this->fixture->setNode($mock)
);
}
/**
* @covers \phpDocumentor\GraphViz\Graph::findNode
*/
public function testFindNode(): void
{
$this->assertNull($this->fixture->findNode('MyNode'));
$mock = m::mock(Node::class);
$mock->expects('getName')->andReturn('MyName');
$this->fixture->setNode($mock);
$this->assertSame(
$mock,
$this->fixture->findNode('MyName')
);
$subGraph = Graph::create();
$mock2 = m::mock(Node::class);
$mock2->expects('getName')->andReturn('MyName2');
$subGraph->setNode($mock2);
$this->fixture->addGraph($subGraph);
$this->assertSame(
$mock2,
$this->fixture->findNode('MyName2')
);
}
/**
* @covers \phpDocumentor\GraphViz\Graph::__set
*/
public function test__set(): void
{
$mock = m::mock(Node::class);
$this->fixture->__set('myNode', $mock);
self::assertSame($mock, $this->fixture->myNode);
}
/**
* @covers \phpDocumentor\GraphViz\Graph::__get
*/
public function test__get(): void
{
$mock = m::mock(Node::class);
$this->fixture->myNode = $mock;
$this->assertSame(
$mock,
$this->fixture->myNode
);
}
/**
* @covers \phpDocumentor\GraphViz\Graph::link
*/
public function testLink(): void
{
$mock = m::mock(Edge::class);
$this->assertSame(
$this->fixture,
$this->fixture->link($mock)
);
}
/**
* @covers \phpDocumentor\GraphViz\Graph::export
*/
public function testExportException(): void
{
$graph = Graph::create('My First Graph');
$filename = tempnam(sys_get_temp_dir(), 'tst');
if ($filename === false) {
$this->assertFalse('Failed to create destination file');
return;
}
$this->expectException(Exception::class);
$graph->export('fpd', $filename);
}
/**
* @covers \phpDocumentor\GraphViz\Graph::export
*/
public function testExport(): void
{
$graph = Graph::create('My First Graph');
$filename = tempnam(sys_get_temp_dir(), 'tst');
if ($filename === false) {
$this->assertFalse('Failed to create destination file');
return;
}
$this->assertSame(
$graph,
$graph->export('pdf', $filename)
);
$this->assertTrue(is_readable($filename));
}
/**
* @covers \phpDocumentor\GraphViz\Graph::__toString
*/
public function test__toString(): void
{
$graph = Graph::create('My First Graph');
$this->assertSame(
$this->normalizeLineEndings((string) $graph),
$this->normalizeLineEndings(('digraph "My First Graph" {' . PHP_EOL . PHP_EOL . '}'))
);
$graph->setLabel('PigeonPost');
$this->assertSame(
$this->normalizeLineEndings((string) $graph),
$this->normalizeLineEndings(('digraph "My First Graph" {' . PHP_EOL . 'label="PigeonPost"' . PHP_EOL . '}'))
);
$graph->setStrict(true);
$this->assertSame(
$this->normalizeLineEndings((string) $graph),
$this->normalizeLineEndings(
('strict digraph "My First Graph" {' . PHP_EOL . 'label="PigeonPost"' . PHP_EOL . '}')
)
);
}
/**
* Help avoid issue of "#Warning: Strings contain different line endings!" on Windows.
*/
private function normalizeLineEndings(string $string): string
{
$result = preg_replace('~\R~u', "\r\n", $string);
if ($result === null) {
throw new RuntimeException('Normalize line endings failed');
}
return $result;
}
}

View File

@@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz\Test;
use phpDocumentor\GraphViz\AttributeNotFound;
use phpDocumentor\GraphViz\Node;
use PHPUnit\Framework\TestCase;
/**
* Test for the the class representing a GraphViz node.
*/
class NodeTest extends TestCase
{
/** @var Node */
protected $fixture = null;
/**
* Initializes the fixture for this test.
*/
protected function setUp(): void
{
$this->fixture = new Node('name', 'label');
}
/**
* Tests the construct method
*
* @covers \phpDocumentor\GraphViz\Node::__construct
* @returnn void
*/
public function testConstruct(): void
{
$fixture = new Node('MyName', 'MyLabel');
$this->assertInstanceOf(
Node::class,
$fixture
);
$this->assertSame('MyName', $fixture->getName());
$this->assertSame('MyLabel', $fixture->getLabel()->getValue());
}
/**
* Tests the create method
*
* @covers \phpDocumentor\GraphViz\Node::create
* @returnn void
*/
public function testCreate(): void
{
$this->assertInstanceOf(
Node::class,
Node::create('name', 'label')
);
}
/**
* Tests the getting and setting of the name.
*
* @covers \phpDocumentor\GraphViz\Node::getName
* @covers \phpDocumentor\GraphViz\Node::setName
*/
public function testName(): void
{
$this->assertSame(
$this->fixture->getName(),
'name',
'Expecting the name to match the initial state'
);
$this->assertSame(
$this->fixture,
$this->fixture->setName('otherName'),
'Expecting a fluent interface'
);
$this->assertSame(
$this->fixture->getName(),
'otherName',
'Expecting the name to contain the new value'
);
}
/**
* Tests the magic __call method, to work as described, return the object
* instance for a setX method, return the value for an getX method, and null
* for the remaining method calls
*
* @covers \phpDocumentor\GraphViz\Node::__call
* @covers \phpDocumentor\GraphViz\Node::getAttribute
* @covers \phpDocumentor\GraphViz\Node::setAttribute
*/
public function testCall(): void
{
$fontname = 'Bitstream Vera Sans';
$this->assertInstanceOf(Node::class, $this->fixture->setfontname($fontname));
$this->assertSame($fontname, $this->fixture->getfontname()->getValue());
$this->assertNull($this->fixture->someNonExistingMethod());
}
/**
* @covers \phpDocumentor\GraphViz\Node::getAttribute
* @covers \phpDocumentor\GraphViz\AttributeNotFound::__construct
*/
public function testGetNonExistingAttributeThrowsAttributeNotFound(): void
{
$this->expectException(AttributeNotFound::class);
$this->expectExceptionMessage('Attribute with name "fontname" was not found');
$this->fixture->getFontname();
}
/**
* Tests whether the magic __toString method returns a well formatted string
* as specified in the DOT standard
*
* @covers \phpDocumentor\GraphViz\Node::__toString
*/
public function testToString(): void
{
$this->fixture->setfontsize(12);
$this->fixture->setfontname('Bitstream Vera Sans');
$dot = <<<DOT
"name" [
label="label"
fontsize="12"
fontname="Bitstream Vera Sans"
]
DOT;
$this->assertSame($dot, (string) $this->fixture);
}
/**
* Tests whether the magic __toString method returns a well formatted string
* as specified in the DOT standard when the label contains slashes.
*
* @covers \phpDocumentor\GraphViz\Node::__toString
*/
public function testToStringWithLabelContainingSlashes(): void
{
$this->fixture->setfontsize(12);
$this->fixture->setfontname('Bitstream Vera Sans');
$this->fixture->setLabel('\phpDocumentor\Descriptor\ProjectDescriptor');
$dot = <<<DOT
"name" [
label="\\\\phpDocumentor\\\\Descriptor\\\\ProjectDescriptor"
fontsize="12"
fontname="Bitstream Vera Sans"
]
DOT;
$this->assertSame($dot, (string) $this->fixture);
}
}

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\GraphViz\PHPStan;
use Mockery as m;
use phpDocumentor\GraphViz\Graph;
use phpDocumentor\GraphViz\Node;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Type\FloatType;
use PHPStan\Type\StringType;
use PHPUnit\Framework\TestCase;
final class MethodReflectionExtensionTest extends TestCase
{
/** @var MethodReflectionExtension */
private $fixture;
public function setUp(): void
{
$this->fixture = new MethodReflectionExtension();
}
/**
* @dataProvider existingMethodProvider
*/
public function testNodeHasMethodReturnsTrue(string $className, string $methodName): void
{
$classReflection = m::mock(ClassReflection::class);
$classReflection->shouldReceive('getName')->andReturn($className);
$this->assertTrue($this->fixture->hasMethod($classReflection, $methodName));
}
/**
* @return array<string, array<string, string>>
*/
public function existingMethodProvider(): array
{
return [
'node::getLabel' => [
'className' => Node::class,
'methodName' => 'getLabel',
],
'node::setLabel' => [
'className' => Node::class,
'methodName' => 'setLabel',
],
'graph::setFontSize' => [
'className' => Graph::class,
'methodName' => 'setFontSize',
],
'graph::getFontSize' => [
'className' => Graph::class,
'methodName' => 'getFontSize',
],
];
}
public function testAttributeType(): void
{
$classReflection = m::mock(ClassReflection::class);
$classReflection->shouldReceive('getName')->andReturn(Node::class);
$method = $this->fixture->getMethod($classReflection, 'setFontSize');
$this->assertInstanceOf(FloatType::class, $method->getVariants()[0]->getParameters()[0]->getType());
}
public function testAttributeTypeOfNoneExisting(): void
{
$classReflection = m::mock(ClassReflection::class);
$classReflection->shouldReceive('getName')->andReturn(Node::class);
$method = $this->fixture->getMethod($classReflection, 'setColor');
$this->assertInstanceOf(StringType::class, $method->getVariants()[0]->getParameters()[0]->getType());
}
}