├── .gitignore
├── .scrutinizer.yml
├── .travis.coverage.sh
├── .travis.yml
├── CONTRIBUTING.md
├── README.md
├── composer.json
├── finalizer
├── phpunit.xml.dist
├── src
└── Finalizer
│ ├── Console
│ └── CheckFinalClassesCommand.php
│ ├── Constraint
│ └── IsFinalizable.php
│ ├── Iterator
│ └── MapIterator.php
│ ├── Reflection
│ └── InheritanceClasses.php
│ └── Scanner
│ ├── DirectoryClassScanner.php
│ └── DirectoryFileScanner.php
└── test
├── FinalizerTest
├── Console
│ └── CheckFinalClassesCommandTest.php
├── Constraint
│ └── IsFinalizableTest.php
├── Iterator
│ └── MapIteratorTest.php
├── Reflection
│ └── InheritanceClassesTest.php
└── Scanner
│ ├── DirectoryClassScannerTest.php
│ └── DirectoryFileScannerTest.php
└── FinalizerTestAsset
├── BarMethodInterface.php
├── EmptyInterface.php
├── Finalizable
├── ClassWithConstructor.php
├── ClassWithNoMethods.php
├── ClassWithPrivateMethod.php
├── ClassWithProtectedMethod.php
├── EmptyChildClass.php
├── FooBarConstructorMethodClass.php
├── FooBarMethodClass.php
├── FooMethodClass.php
├── InvokableClass.php
└── InvokableClassWithConstructor.php
├── FooMethodInterface.php
├── InvalidFinal
└── ClassThatShouldNotBeFinal.php
├── NonFinalizable
├── AbstractEmptyClass.php
├── AbstractEmptyClassImplementingEmptyInterface.php
├── ClassWithNoMethods.php
├── EmptyParentClass.php
├── FooBarMethodClass.php
└── InvokableClassWithAdditionalMethods.php
└── Scanner
└── DirectoryFileScanner
├── DirWithOneHhFile
└── 1.hh
├── DirWithOnePhpFile
└── 1.php
├── DirWithOneTxtFile
└── 1.txt
├── DirWithTwoPhpFiles
├── 1.php
└── 2.php
└── EmptyDirectory
└── .gitignore
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.phar
2 | composer.lock
3 | vendor/
4 | clover.xml
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | before_commands:
2 | - "composer install --no-dev --prefer-source"
3 |
4 | tools:
5 | external_code_coverage:
6 | timeout: 600
7 | php_code_coverage:
8 | enabled: true
9 | php_code_sniffer:
10 | enabled: true
11 | config:
12 | standard: PSR2
13 | filter:
14 | paths: ["src/*", "test/*"]
15 | php_cpd:
16 | enabled: true
17 | excluded_dirs: ["test", "vendor"]
18 | php_cs_fixer:
19 | enabled: true
20 | config:
21 | level: all
22 | filter:
23 | paths: ["src/*", "test/*"]
24 | php_loc:
25 | enabled: true
26 | excluded_dirs: ["test", "vendor"]
27 | php_mess_detector:
28 | enabled: true
29 | filter:
30 | paths: ["src/*"]
31 | php_pdepend:
32 | enabled: true
33 | excluded_dirs: ["test", "vendor"]
34 | php_analyzer:
35 | enabled: true
36 | filter:
37 | paths: ["src/*", "test/*"]
38 | php_hhvm:
39 | enabled: true
40 | filter:
41 | paths: ["src/*", "test/*"]
42 | sensiolabs_security_checker: true
43 |
--------------------------------------------------------------------------------
/.travis.coverage.sh:
--------------------------------------------------------------------------------
1 | set -x
2 | if [ "$TRAVIS_PHP_VERSION" = 'hhvm' ] || [ "$TRAVIS_PHP_VERSION" = 'hhvm-nightly' ] ; then
3 | echo "skipping coverage submission for HHVM builds"
4 | else
5 | wget https://scrutinizer-ci.com/ocular.phar
6 | php ocular.phar code-coverage:upload --format=php-clover ./clover.xml
7 | fi
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.2
5 | - nightly
6 |
7 | before_script:
8 | - composer self-update
9 | - composer update
10 |
11 | script:
12 | - ./vendor/bin/phpunit --disallow-test-output --coverage-clover ./clover.xml
13 | - ./finalizer finalizer:check-final-classes src
14 |
15 | matrix:
16 | allow_failures:
17 | - php: nightly
18 |
19 | after_script:
20 | - sh .travis.coverage.sh
21 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | * Coding standard for the project is [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
4 | * The project will follow strict [object calisthenics](http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php)
5 | * Any contribution must provide tests for additional introduced conditions
6 | * Any un-confirmed issue needs a failing test case before being accepted
7 | * Pull requests must be sent from a new hotfix/feature branch, not from `master`.
8 |
9 | ## Installation
10 |
11 | To install the project and run the tests, you need to clone it first:
12 |
13 | ```sh
14 | $ git clone git://github.com/Ocramius/Finalizer.git
15 | ```
16 |
17 | You will then need to run a composer installation:
18 |
19 | ```sh
20 | $ cd Finalizer
21 | $ curl -s https://getcomposer.org/installer | php
22 | $ php composer.phar update
23 | ```
24 |
25 | ## Testing
26 |
27 | The PHPUnit version to be used is the one installed as a dev- dependency via composer:
28 |
29 | ```sh
30 | $ ./vendor/bin/phpunit
31 | ```
32 |
33 | Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement
34 | won't be merged.
35 |
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Finalizer
2 |
3 | This library aims at providing simple tools that help deciding whether
4 | a class should or shouldn't be declared as `final`.
5 |
6 | [](https://travis-ci.org/Ocramius/Finalizer)
7 | [](https://scrutinizer-ci.com/g/Ocramius/Finalizer/?branch=master)
8 | [](https://scrutinizer-ci.com/g/Ocramius/Finalizer/?branch=master)
9 | [](https://packagist.org/packages/ocramius/finalizer)
10 | [](https://packagist.org/packages/ocramius/finalizer)
11 |
12 | ## Help/Support
13 |
14 | [](https://gitter.im/Ocramius/Finalizer)
15 |
16 | ## Installation
17 |
18 | Install via [composer](https://getcomposer.org/):
19 |
20 | ```sh
21 | php composer.phar require ocramius/finalizer:~1.0
22 | ```
23 |
24 | ## Usage
25 |
26 | In your console, simply type:
27 |
28 | ```php
29 | ./vendor/bin/finalizer finalizer:check-final-classes path/to/directory
30 | ./vendor/bin/finalizer finalizer:check-final-classes also/supports multiple/directories as/parameters
31 | ```
32 |
33 | Note that finalizer will take decisions on whether classes should or
34 | shouldn't be `final` depending on the classes defined in the directories
35 | that you passed to it.
36 |
37 | Additionally, be aware that `finalizer` will (in its current state) require
38 | any of the PHP or Hack files in the given directories and include them via
39 | `require_once`.
40 |
41 | ## Reference
42 |
43 | If you need to know more about why I wrote this library, and what kind of
44 | decisions it is doing, then please read
45 | [this blogpost about the usage of the `final` keyword](http://goo.gl/4eCCIK).
46 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ocramius/finalizer",
3 | "description": "A simple utility library that checks for PHP classes that should or shouldn't be marked as final",
4 | "type": "library",
5 | "license": "MIT",
6 | "homepage": "https://github.com/Ocramius/Finalizer",
7 | "keywords": [
8 | "final",
9 | "syntax",
10 | "code quality"
11 | ],
12 | "authors": [
13 | {
14 | "name": "Marco Pivetta",
15 | "email": "ocramius@gmail.com",
16 | "homepage": "http://ocramius.github.io/"
17 | }
18 | ],
19 | "autoload": {
20 | "psr-4": {
21 | "Finalizer\\": "src/Finalizer"
22 | }
23 | },
24 | "autoload-dev": {
25 | "psr-4": {
26 | "FinalizerTest\\": "test/FinalizerTest/",
27 | "FinalizerTestAsset\\": "test/FinalizerTestAsset/"
28 | }
29 | },
30 | "require": {
31 | "php": "~7.2",
32 | "symfony/console": "~2.6|~3.0|~4.0"
33 | },
34 | "require-dev": {
35 | "phpunit/phpunit": "~7.0"
36 | },
37 | "bin": [
38 | "finalizer"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/finalizer:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | add(new CheckFinalClassesCommand());
28 |
29 | $cli->run();
30 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 | ./test/FinalizerTest
11 |
12 |
13 |
14 | ./src
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/Finalizer/Console/CheckFinalClassesCommand.php:
--------------------------------------------------------------------------------
1 | setDescription(
26 | 'Checks the given paths for classes that should or shouldn\'t be final'
27 | )
28 | ->setDefinition([new InputArgument(
29 | 'directories',
30 | InputArgument::REQUIRED | InputArgument::IS_ARRAY,
31 | 'Paths to be checked for existing classes'
32 | )]);
33 | $this->setHelp(<<%command.name% command generates a short report
35 | of classes that should be final and classes that shouldn't be final.
36 |
37 | You can use this command as following:
38 | %command.name% path/to/sources
39 | %command.name% path/to/first/sources/dir path/to/second/sources/dir
40 | EOT
41 | );
42 | }
43 |
44 | /**
45 | * {@inheritDoc}
46 | */
47 | public function execute(InputInterface $input, OutputInterface $output) : int
48 | {
49 | $classScanner = new DirectoryClassScanner();
50 | $fileScanner = new DirectoryFileScanner();
51 | $classes = $classScanner($fileScanner($input->getArgument('directories')));
52 |
53 | $finalizeStatusCode = $this->renderClassesToFinalize($classes, $output);
54 | $deFinalizeStatusCode = $this->renderClassesToDeFinalize($classes, $output);
55 |
56 | return max($finalizeStatusCode, $deFinalizeStatusCode);
57 | }
58 |
59 | /**
60 | * @param \ReflectionClass[] $classes
61 | */
62 | private function renderClassesToFinalize(array $classes, OutputInterface $output) : int
63 | {
64 | $tableHelper = new Table($output);
65 |
66 | $toFinalize = $this->getClassesToFinalize($classes);
67 | if (count($toFinalize) > 0) {
68 | $output->writeln('Following classes need to be made final:');
69 |
70 | $tableHelper->addRows(array_map(
71 | function (\ReflectionClass $class) {
72 | return [$class->getName()];
73 | },
74 | $toFinalize
75 | ));
76 |
77 | $tableHelper->render();
78 |
79 | return 1;
80 | }
81 |
82 | return 0;
83 | }
84 |
85 | /**
86 | * @param \ReflectionClass[] $classes
87 | */
88 | private function renderClassesToDeFinalize(array $classes, OutputInterface $output) : int
89 | {
90 | $tableHelper = new Table($output);
91 |
92 | $toDeFinalize = $this->getClassesToDeFinalize($classes);
93 | if (count($toDeFinalize) > 0) {
94 | $output->writeln('Following classes are final and need to be made extensible again:');
95 |
96 | $tableHelper->addRows(array_map(
97 | function (\ReflectionClass $class) {
98 | return [$class->getName()];
99 | },
100 | $toDeFinalize
101 | ));
102 |
103 | $tableHelper->render();
104 |
105 | return 1;
106 | }
107 |
108 | return 0;
109 | }
110 |
111 | /**
112 | * @param \ReflectionClass[] $classes
113 | *
114 | * @return \ReflectionClass[]
115 | */
116 | private function getClassesToFinalize(array $classes)
117 | {
118 | $isFinalizable = new IsFinalizable();
119 | $toFinalize = [];
120 |
121 | foreach ($classes as $class) {
122 | if ($isFinalizable($class, ...$classes) && ! $class->isFinal()) {
123 | $toFinalize[] = $class;
124 | }
125 | }
126 |
127 | return $toFinalize;
128 | }
129 |
130 | /**
131 | * @param \ReflectionClass[] $classes
132 | *
133 | * @return \ReflectionClass[]
134 | */
135 | private function getClassesToDeFinalize(array $classes)
136 | {
137 | $isFinalizable = new IsFinalizable();
138 | $toDeFinalize = [];
139 |
140 | foreach ($classes as $class) {
141 | if ((! $isFinalizable($class, ...$classes)) && $class->isFinal()) {
142 | $toDeFinalize[] = $class;
143 | }
144 | }
145 |
146 | return $toDeFinalize;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/Finalizer/Constraint/IsFinalizable.php:
--------------------------------------------------------------------------------
1 | isAbstract()
16 | && ! $this->hasChildClasses($class, $definedClasses)
17 | && (
18 | ($class->getInterfaces() && $this->implementsOnlyInterfaceMethods($class))
19 | || $this->isOnlyInvokable($class)
20 | );
21 | }
22 |
23 | /**
24 | * Checks whether a given class is an invokable, and whether no other methods are implemented on it
25 | *
26 | * @param \ReflectionClass $class
27 | *
28 | * @return bool
29 | */
30 | private function isOnlyInvokable(\ReflectionClass $class)
31 | {
32 | return ['__invoke'] === array_values(array_map('strtolower', $this->getNonConstructorMethodNames($class)))
33 | && ! $class->getMethod('__invoke')->isStatic();
34 | }
35 |
36 | /**
37 | * @param \ReflectionClass $class
38 | * @param \ReflectionClass[] $definedClasses
39 | *
40 | * @return bool
41 | */
42 | private function hasChildClasses(\ReflectionClass $class, array $definedClasses)
43 | {
44 | return array_filter(
45 | $definedClasses,
46 | function (\ReflectionClass $childClassCandidate) use ($class) {
47 | $parentClass = $childClassCandidate->getParentClass();
48 |
49 | if (! $parentClass) {
50 | return false;
51 | }
52 |
53 | return $parentClass->getName() === $class->getName();
54 | }
55 | );
56 | }
57 |
58 | /**
59 | * Checks whether all methods implemented by $class are defined in
60 | * interfaces implemented by $class
61 | *
62 | * @param \ReflectionClass $class
63 | *
64 | * @return bool
65 | */
66 | private function implementsOnlyInterfaceMethods(\ReflectionClass $class)
67 | {
68 | return ! array_diff(
69 | $this->getNonConstructorMethodNames($class),
70 | array_merge(
71 | [],
72 | [],
73 | ...array_values(array_map(
74 | [$this, 'getNonConstructorMethodNames'],
75 | $class->getInterfaces()
76 | ))
77 | )
78 | );
79 | }
80 |
81 | /**
82 | * @param \ReflectionClass $class
83 | *
84 | * @return string[] (indexed numerically)
85 | */
86 | private function getNonConstructorMethodNames(\ReflectionClass $class)
87 | {
88 | return array_values(array_map(
89 | function (\ReflectionMethod $method) {
90 | return $method->getName();
91 | },
92 | array_filter(
93 | $class->getMethods(),
94 | function (\ReflectionMethod $method) {
95 | return $method->isPublic() && ! $method->isConstructor();
96 | }
97 | )
98 | ));
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/Finalizer/Iterator/MapIterator.php:
--------------------------------------------------------------------------------
1 | wrappedIterator = $wrappedIterator;
24 | $this->map = $map;
25 | }
26 |
27 | /**
28 | * {@inheritDoc}
29 | */
30 | public function current()
31 | {
32 | $map = $this->map;
33 |
34 | return $map($this->wrappedIterator->current());
35 | }
36 |
37 | /**
38 | * {@inheritDoc}
39 | */
40 | public function next()
41 | {
42 | $this->wrappedIterator->next();
43 | }
44 |
45 | /**
46 | * {@inheritDoc}
47 | */
48 | public function key()
49 | {
50 | return $this->wrappedIterator->key();
51 | }
52 |
53 | /**
54 | * {@inheritDoc}
55 | */
56 | public function valid()
57 | {
58 | return $this->wrappedIterator->valid();
59 | }
60 |
61 | /**
62 | * {@inheritDoc}
63 | */
64 | public function rewind()
65 | {
66 | $this->wrappedIterator->rewind();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Finalizer/Reflection/InheritanceClasses.php:
--------------------------------------------------------------------------------
1 | getParentClass()) {
15 | return array_merge(
16 | [$class->getName() => $class],
17 | $this->__invoke($parentClass)
18 | );
19 | }
20 |
21 | return [$class->getName() => $class];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Finalizer/Scanner/DirectoryClassScanner.php:
--------------------------------------------------------------------------------
1 | getClassesDeclaredInFiles($this->includeFiles($files), get_declared_classes());
15 | }
16 |
17 | /**
18 | * @param string[]|\Traversable $files
19 | *
20 | * @return bool[] the files that were loaded, indexed by file name
21 | */
22 | private function includeFiles($files)
23 | {
24 | $loadedFiles = [];
25 |
26 | foreach ($files as $file) {
27 | require_once $file;
28 |
29 | $loadedFiles[realpath($file)] = true;
30 | }
31 |
32 | return $loadedFiles;
33 | }
34 |
35 | /**
36 | * @param string[] $files
37 | * @param string[] $classes
38 | *
39 | * @return \ReflectionClass[]
40 | */
41 | private function getClassesDeclaredInFiles(array $files, array $classes)
42 | {
43 | return array_filter(
44 | array_map(
45 | function ($className) {
46 | return new \ReflectionClass($className);
47 | },
48 | $classes
49 | ),
50 | function (\ReflectionClass $class) use ($files) {
51 | return isset($files[realpath($class->getFileName())]);
52 | }
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Finalizer/Scanner/DirectoryFileScanner.php:
--------------------------------------------------------------------------------
1 | run(new ArrayInput(['directories' => $paths]), $output);
28 |
29 | $this->assertCount(count(explode("\n", $expectedOutput)), explode(PHP_EOL, $output->fetch()));
30 | $this->assertSame($expectedExitCode, $exitCode);
31 | }
32 |
33 | /**
34 | * Data provider
35 | *
36 | * @return mixed[][]
37 | */
38 | public function pathsProvider() : array
39 | {
40 | return [
41 | [
42 | [__DIR__ . '/../../../src'],
43 | '',
44 | 0,
45 | ],
46 | [
47 | [__DIR__ . '/../../FinalizerTestAsset'],
48 | <<<'OUTPUT'
49 | Following classes need to be made final:
50 | +--------------------------------------------------------------+
51 | | FinalizerTestAsset\Finalizable\ClassWithConstructor |
52 | | FinalizerTestAsset\Finalizable\ClassWithNoMethods |
53 | | FinalizerTestAsset\Finalizable\ClassWithPrivateMethod |
54 | | FinalizerTestAsset\Finalizable\ClassWithProtectedMethod |
55 | | FinalizerTestAsset\Finalizable\EmptyChildClass |
56 | | FinalizerTestAsset\Finalizable\FooBarConstructorMethodClass |
57 | | FinalizerTestAsset\Finalizable\FooBarMethodClass |
58 | | FinalizerTestAsset\Finalizable\FooMethodClass |
59 | | FinalizerTestAsset\Finalizable\InvokableClass |
60 | | FinalizerTestAsset\Finalizable\InvokableClassWithConstructor |
61 | +--------------------------------------------------------------+
62 | Following classes are final and need to be made extensible again:
63 | +-----------------------------------------------------------+
64 | | FinalizerTestAsset\InvalidFinal\ClassThatShouldNotBeFinal |
65 | +-----------------------------------------------------------+
66 |
67 | OUTPUT
68 | ,
69 | 1
70 | ],
71 | [
72 | [__DIR__ . '/../../../src', __DIR__ . '/../../FinalizerTestAsset'],
73 | <<<'OUTPUT'
74 | Following classes need to be made final:
75 | +--------------------------------------------------------------+
76 | | FinalizerTestAsset\Finalizable\ClassWithConstructor |
77 | | FinalizerTestAsset\Finalizable\ClassWithNoMethods |
78 | | FinalizerTestAsset\Finalizable\ClassWithPrivateMethod |
79 | | FinalizerTestAsset\Finalizable\ClassWithProtectedMethod |
80 | | FinalizerTestAsset\Finalizable\EmptyChildClass |
81 | | FinalizerTestAsset\Finalizable\FooBarConstructorMethodClass |
82 | | FinalizerTestAsset\Finalizable\FooBarMethodClass |
83 | | FinalizerTestAsset\Finalizable\FooMethodClass |
84 | | FinalizerTestAsset\Finalizable\InvokableClass |
85 | | FinalizerTestAsset\Finalizable\InvokableClassWithConstructor |
86 | +--------------------------------------------------------------+
87 | Following classes are final and need to be made extensible again:
88 | +-----------------------------------------------------------+
89 | | FinalizerTestAsset\InvalidFinal\ClassThatShouldNotBeFinal |
90 | +-----------------------------------------------------------+
91 |
92 | OUTPUT
93 | ,
94 | 1
95 | ],
96 | [
97 | [
98 | __DIR__ . '/../../FinalizerTestAsset/Finalizable',
99 | __DIR__ . '/../../FinalizerTestAsset/InvalidFinal',
100 | ],
101 | <<<'OUTPUT'
102 | Following classes need to be made final:
103 | +--------------------------------------------------------------+
104 | | FinalizerTestAsset\Finalizable\ClassWithConstructor |
105 | | FinalizerTestAsset\Finalizable\ClassWithNoMethods |
106 | | FinalizerTestAsset\Finalizable\ClassWithPrivateMethod |
107 | | FinalizerTestAsset\Finalizable\ClassWithProtectedMethod |
108 | | FinalizerTestAsset\Finalizable\EmptyChildClass |
109 | | FinalizerTestAsset\Finalizable\FooBarConstructorMethodClass |
110 | | FinalizerTestAsset\Finalizable\FooBarMethodClass |
111 | | FinalizerTestAsset\Finalizable\FooMethodClass |
112 | | FinalizerTestAsset\Finalizable\InvokableClass |
113 | | FinalizerTestAsset\Finalizable\InvokableClassWithConstructor |
114 | +--------------------------------------------------------------+
115 | Following classes are final and need to be made extensible again:
116 | +-----------------------------------------------------------+
117 | | FinalizerTestAsset\InvalidFinal\ClassThatShouldNotBeFinal |
118 | +-----------------------------------------------------------+
119 |
120 | OUTPUT
121 | ,
122 | 1
123 | ],
124 | ];
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/test/FinalizerTest/Constraint/IsFinalizableTest.php:
--------------------------------------------------------------------------------
1 | assertSame(
26 | $finalizable,
27 | (new IsFinalizable())
28 | ->__invoke(
29 | new \ReflectionClass($className),
30 | ...array_map(
31 | function ($className) {
32 | return new \ReflectionClass($className);
33 | },
34 | $definedClasses
35 | )
36 | )
37 | );
38 | }
39 |
40 | /**
41 | * @return \ReflectionClass[][]|\ReflectionClass[][][]
42 | */
43 | public function finalizableClassesProvider()
44 | {
45 | return [
46 | NonFinalizable\EmptyParentClass::class => [
47 | NonFinalizable\EmptyParentClass::class,
48 | [
49 | Finalizable\EmptyChildClass::class,
50 | ],
51 | ],
52 | Finalizable\EmptyChildClass::class => [
53 | Finalizable\EmptyChildClass::class,
54 | [],
55 | ],
56 | NonFinalizable\ClassWithNoMethods::class => [
57 | NonFinalizable\ClassWithNoMethods::class,
58 | [],
59 | ],
60 | Finalizable\ClassWithNoMethods::class => [
61 | Finalizable\ClassWithNoMethods::class,
62 | [],
63 | ],
64 | Finalizable\FooMethodClass::class => [
65 | Finalizable\FooMethodClass::class,
66 | [],
67 | ],
68 | Finalizable\FooBarMethodClass::class => [
69 | Finalizable\FooBarMethodClass::class,
70 | [],
71 | ],
72 | NonFinalizable\FooBarMethodClass::class => [
73 | NonFinalizable\FooBarMethodClass::class,
74 | [],
75 | ],
76 | Finalizable\InvokableClass::class => [
77 | Finalizable\InvokableClass::class,
78 | [],
79 | ],
80 | NonFinalizable\InvokableClassWithAdditionalMethods::class => [
81 | NonFinalizable\InvokableClassWithAdditionalMethods::class,
82 | [],
83 | ],
84 | Finalizable\InvokableClassWithConstructor::class => [
85 | Finalizable\InvokableClassWithConstructor::class,
86 | [],
87 | ],
88 | Finalizable\ClassWithConstructor::class => [
89 | Finalizable\ClassWithConstructor::class,
90 | [],
91 | ],
92 | Finalizable\FooBarConstructorMethodClass::class => [
93 | Finalizable\FooBarConstructorMethodClass::class,
94 | [],
95 | ],
96 | Finalizable\ClassWithPrivateMethod::class => [
97 | Finalizable\ClassWithPrivateMethod::class,
98 | [],
99 | ],
100 | Finalizable\ClassWithProtectedMethod::class => [
101 | Finalizable\ClassWithProtectedMethod::class,
102 | [],
103 | ],
104 | ];
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/test/FinalizerTest/Iterator/MapIteratorTest.php:
--------------------------------------------------------------------------------
1 | createPartialMock(\stdClass::class, ['__invoke']);
17 | /* @var $wrappedIterator \Iterator|\PHPUnit_Framework_MockObject_MockObject */
18 | $wrappedIterator = $this->createMock(\Iterator::class);
19 |
20 | $wrappedIterator->expects($this->at(0))->method('current')->will($this->returnValue('foo'));
21 | $wrappedIterator->expects($this->at(1))->method('current')->will($this->returnValue('bar'));
22 |
23 | $map->expects($this->at(0))->method('__invoke')->with('foo')->will($this->returnValue('baz'));
24 | $map->expects($this->at(1))->method('__invoke')->with('bar')->will($this->returnValue('tab'));
25 |
26 | $iterator = new MapIterator($wrappedIterator, $map);
27 |
28 | $this->assertSame('baz', $iterator->current());
29 | $this->assertSame('tab', $iterator->current());
30 | }
31 |
32 | public function testNext()
33 | {
34 | /* @var $map callable|\PHPUnit_Framework_MockObject_MockObject */
35 | $map = $this->createPartialMock(\stdClass::class, ['__invoke']);
36 | /* @var $wrappedIterator \Iterator|\PHPUnit_Framework_MockObject_MockObject */
37 | $wrappedIterator = $this->createMock(\Iterator::class);
38 |
39 | $wrappedIterator->expects($this->exactly(2))->method('next');
40 |
41 | $map->expects($this->never())->method('__invoke');
42 |
43 | $iterator = new MapIterator($wrappedIterator, $map);
44 |
45 | $iterator->next();
46 | $iterator->next();
47 | }
48 |
49 | public function testRewind()
50 | {
51 | /* @var $map callable|\PHPUnit_Framework_MockObject_MockObject */
52 | $map = $this->createPartialMock(\stdClass::class, ['__invoke']);
53 | /* @var $wrappedIterator \Iterator|\PHPUnit_Framework_MockObject_MockObject */
54 | $wrappedIterator = $this->createMock(\Iterator::class);
55 |
56 | $wrappedIterator->expects($this->exactly(2))->method('rewind');
57 |
58 | $map->expects($this->never())->method('__invoke');
59 |
60 | $iterator = new MapIterator($wrappedIterator, $map);
61 |
62 | $iterator->rewind();
63 | $iterator->rewind();
64 | }
65 |
66 | public function testKey()
67 | {
68 | /* @var $map callable|\PHPUnit_Framework_MockObject_MockObject */
69 | $map = $this->createPartialMock(\stdClass::class, ['__invoke']);
70 | /* @var $wrappedIterator \Iterator|\PHPUnit_Framework_MockObject_MockObject */
71 | $wrappedIterator = $this->createMock(\Iterator::class);
72 |
73 | $wrappedIterator->expects($this->at(0))->method('key')->will($this->returnValue('foo'));
74 | $wrappedIterator->expects($this->at(1))->method('key')->will($this->returnValue('bar'));
75 |
76 | $map->expects($this->never())->method('__invoke');
77 |
78 | $iterator = new MapIterator($wrappedIterator, $map);
79 |
80 | $this->assertSame('foo', $iterator->key());
81 | $this->assertSame('bar', $iterator->key());
82 | }
83 |
84 | public function testValid()
85 | {
86 | /* @var $map callable|\PHPUnit_Framework_MockObject_MockObject */
87 | $map = $this->createPartialMock(\stdClass::class, ['__invoke']);
88 | /* @var $wrappedIterator \Iterator|\PHPUnit_Framework_MockObject_MockObject */
89 | $wrappedIterator = $this->createMock(\Iterator::class);
90 |
91 | $wrappedIterator->expects($this->at(0))->method('valid')->will($this->returnValue(true));
92 | $wrappedIterator->expects($this->at(1))->method('valid')->will($this->returnValue(false));
93 |
94 | $map->expects($this->never())->method('__invoke');
95 |
96 | $iterator = new MapIterator($wrappedIterator, $map);
97 |
98 | $this->assertTrue($iterator->valid());
99 | $this->assertFalse($iterator->valid());
100 | }
101 |
102 | /**
103 | * @coversNothing
104 | */
105 | public function testIterationWithActualArrayIterator()
106 | {
107 | $this->assertEquals(
108 | [2, 4, 6],
109 | iterator_to_array(new MapIterator(
110 | new \ArrayIterator([1, 2, 3]),
111 | function ($value) {
112 | return $value * 2;
113 | }
114 | ))
115 | );
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/test/FinalizerTest/Reflection/InheritanceClassesTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(
32 | $indexedClasses,
33 | (new InheritanceClasses())->__invoke(new \ReflectionClass($className))
34 | );
35 | }
36 |
37 | /**
38 | * @return string[][]|string[][][]
39 | */
40 | public function classesProvider()
41 | {
42 | return [
43 | 'class with no parent class' => [
44 | \Exception::class,
45 | [
46 | \Exception::class,
47 | ],
48 | false,
49 | ],
50 | 'class with one parent class' => [
51 | \LogicException::class,
52 | [
53 | \LogicException::class,
54 | \Exception::class,
55 | ],
56 | false,
57 | ],
58 | 'class with multiple parent classes' => [
59 | \InvalidArgumentException::class,
60 | [
61 | \InvalidArgumentException::class,
62 | \LogicException::class,
63 | \Exception::class,
64 | ],
65 | false,
66 | ],
67 | ];
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/test/FinalizerTest/Scanner/DirectoryClassScannerTest.php:
--------------------------------------------------------------------------------
1 | getName();
24 | },
25 | (new DirectoryClassScanner())->__invoke($paths)
26 | ));
27 |
28 | sort($found);
29 |
30 | $this->assertEquals($expectedClasses, $found);
31 | }
32 |
33 | /**
34 | * @dataProvider pathsProvider
35 | *
36 | * @param string[]|array $paths
37 | * @param string[] $expectedClasses
38 | */
39 | public function testDirectoryScannerWithIterator(array $paths, $expectedClasses)
40 | {
41 | $this->testDirectoryScannerWithPaths(new \ArrayIterator($paths), $expectedClasses);
42 | }
43 |
44 | /**
45 | * @return string[][][]
46 | */
47 | public function pathsProvider()
48 | {
49 | return [
50 | [
51 | [__FILE__],
52 | [__CLASS__],
53 | ],
54 | [
55 | [__FILE__, __DIR__ . '/DirectoryFileScannerTest.php'],
56 | [__CLASS__, DirectoryFileScannerTest::class],
57 | ],
58 | [
59 | [
60 | __FILE__,
61 | __DIR__ . '/../../FinalizerTestAsset/Scanner/DirectoryFileScanner/DirWithOnePhpFile/1.php'
62 | ],
63 | [__CLASS__],
64 | ],
65 | ];
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/test/FinalizerTest/Scanner/DirectoryFileScannerTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(\Traversable::class, (new DirectoryFileScanner())->__invoke($paths));
22 |
23 | foreach ((new DirectoryFileScanner())->__invoke($paths) as $path) {
24 | $this->assertInternalType('string', $path);
25 | }
26 | }
27 |
28 | /**
29 | * @dataProvider pathsProvider
30 | *
31 | * @param string[] $paths
32 | */
33 | public function testDiscoversCorrectAmountOfFiles(array $paths, $count)
34 | {
35 | $this->assertCount($count, iterator_to_array((new DirectoryFileScanner())->__invoke($paths)));
36 | }
37 |
38 | /**
39 | * Data provider
40 | *
41 | * @return string[][][]|int[][]
42 | */
43 | public function pathsProvider()
44 | {
45 | return [
46 | [
47 | [__DIR__ . '/../../FinalizerTestAsset/Scanner/DirectoryFileScanner'],
48 | 4
49 | ],
50 | [
51 | [__DIR__ . '/../../FinalizerTestAsset/Scanner/DirectoryFileScanner/EmptyDirectory'],
52 | 0
53 | ],
54 | [
55 | [__DIR__ . '/../../FinalizerTestAsset/Scanner/DirectoryFileScanner/DirWithOnePhpFile'],
56 | 1
57 | ],
58 | [
59 | [__DIR__ . '/../../FinalizerTestAsset/Scanner/DirectoryFileScanner/DirWithTwoPhpFiles'],
60 | 2
61 | ],
62 | [
63 | [__DIR__ . '/../../FinalizerTestAsset/Scanner/DirectoryFileScanner/DirWithOneHhFile'],
64 | 1
65 | ],
66 | [
67 | [
68 | __DIR__ . '/../../FinalizerTestAsset/Scanner/DirectoryFileScanner/DirWithOnePhpFile',
69 | __DIR__ . '/../../FinalizerTestAsset/Scanner/DirectoryFileScanner/DirWithOneHhFile',
70 | ],
71 | 2
72 | ],
73 | [
74 | [
75 | __DIR__ . '/../../FinalizerTestAsset/Scanner/DirectoryFileScanner/DirWithOnePhpFile',
76 | __DIR__ . '/../../FinalizerTestAsset/Scanner/DirectoryFileScanner/DirWithOneHhFile',
77 | __DIR__ . '/../../FinalizerTestAsset/Scanner/DirectoryFileScanner/DirWithTwoPhpFiles',
78 | ],
79 | 4
80 | ],
81 | ];
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/test/FinalizerTestAsset/BarMethodInterface.php:
--------------------------------------------------------------------------------
1 |