Completed
Push — develop ( 37deef...f45442 )
by Jaap
05:54
created

phpDocumentor/Compiler/Pass/PackageTreeBuilder.php (2 issues)

strict.coding_against_concrete_implementation

Bug Minor

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * phpDocumentor
4
 *
5
 * PHP Version 5.3
6
 *
7
 * @copyright 2010-2014 Mike van Riel / Naenius (http://www.naenius.com)
8
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
9
 * @link      http://phpdoc.org
10
 */
11
12
namespace phpDocumentor\Compiler\Pass;
13
14
use phpDocumentor\Compiler\CompilerPassInterface;
15
use phpDocumentor\Descriptor\Collection;
16
use phpDocumentor\Descriptor\DescriptorAbstract;
17
use phpDocumentor\Descriptor\PackageDescriptor;
18
use phpDocumentor\Descriptor\ProjectDescriptor;
19
use phpDocumentor\Descriptor\TagDescriptor;
20
21
/**
22
 * Rebuilds the package tree from the elements found in files.
23
 *
24
 * On every compiler pass is the package tree rebuild to aid in the process
25
 * of incremental updates.
26
 *
27
 * If the package tree were to be persisted then both locations needed to be
28
 * invalidated if a file were to change.
29
 */
30
class PackageTreeBuilder implements CompilerPassInterface
31
{
32
    const COMPILER_PRIORITY = 9001;
33
34
    /**
35
     * {@inheritDoc}
36
     */
37
    public function getDescription()
38
    {
39
        return 'Build "packages" index';
40
    }
41
42
    /**
43
     * Compiles a 'packages' index on the project and create all Package Descriptors necessary.
44
     *
45
     * @param ProjectDescriptor $project
46
     *
47
     * @return void
48
     */
49
    public function execute(ProjectDescriptor $project)
50
    {
51
        $rootPackageDescriptor = new PackageDescriptor();
52
        $rootPackageDescriptor->setName('\\');
53
        $project->getIndexes()->set('packages', new Collection());
54
        $project->getIndexes()->packages['\\'] = $rootPackageDescriptor;
55
56
        foreach ($project->getFiles() as $file) {
57
            $this->addElementsOfTypeToPackage($project, array($file), 'files');
58
            $this->addElementsOfTypeToPackage($project, $file->getConstants()->getAll(), 'constants');
59
            $this->addElementsOfTypeToPackage($project, $file->getFunctions()->getAll(), 'functions');
60
            $this->addElementsOfTypeToPackage($project, $file->getClasses()->getAll(), 'classes');
61
            $this->addElementsOfTypeToPackage($project, $file->getInterfaces()->getAll(), 'interfaces');
62
            $this->addElementsOfTypeToPackage($project, $file->getTraits()->getAll(), 'traits');
63
        }
64
    }
65
66
    /**
67
     * Adds the given elements of a specific type to their respective Package Descriptors.
68
     *
69
     * This method will assign the given elements to the package as registered in the package field of that
70
     * element. If a package does not exist yet it will automatically be created.
71
     *
72
     * @param ProjectDescriptor    $project
73
     * @param DescriptorAbstract[] $elements Series of elements to add to their respective package.
74
     * @param string               $type     Declares which field of the package will be populated with the given
75
     * series of elements. This name will be transformed to a getter which must exist. Out of performance
76
     * considerations will no effort be done to verify whether the provided type is valid.
77
     *
78
     * @return void
79
     */
80
    protected function addElementsOfTypeToPackage(ProjectDescriptor $project, array $elements, $type)
81
    {
82
        /** @var DescriptorAbstract $element */
83
        foreach ($elements as $element) {
84
            $packageName = '';
85
            $packageTags = $element->getTags()->get('package');
86
            if ($packageTags instanceof Collection) {
87
                $packageTag = $packageTags->getIterator()->current();
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Traversable as the method current() does only exist in the following implementations of said interface: APCUIterator, AppendIterator, ArrayIterator, Behat\Behat\Gherkin\Spec...ion\LazyFeatureIterator, Behat\Testwork\Specifica...edSpecificationIterator, Behat\Testwork\Specifica...oSpecificationsIterator, Behat\Testwork\Specifica...cificationArrayIterator, CachingIterator, CallbackFilterIterator, CoreTestIterator, DirectoryIterator, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, NoRewindIterator, PHPUnit\Framework\TestSuiteIterator, PHPUnit\Runner\Filter\ExcludeGroupFilterIterator, PHPUnit\Runner\Filter\GroupFilterIterator, PHPUnit\Runner\Filter\IncludeGroupFilterIterator, PHPUnit\Runner\Filter\NameFilterIterator, PHP_Token_Stream, ParentIterator, Phar, PharData, PharIo\Manifest\AuthorCollectionIterator, PharIo\Manifest\AuthorElementCollection, PharIo\Manifest\BundledComponentCollectionIterator, PharIo\Manifest\ComponentElementCollection, PharIo\Manifest\ElementCollection, PharIo\Manifest\ExtElementCollection, PharIo\Manifest\RequirementCollectionIterator, Pimple\ServiceIterator, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SebastianBergmann\CodeCoverage\Node\Iterator, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, Symfony\Component\Finder...or\CustomFilterIterator, Symfony\Component\Finder...DateRangeFilterIterator, Symfony\Component\Finder...epthRangeFilterIterator, Symfony\Component\Finder...DirectoryFilterIterator, Symfony\Component\Finder...rator\FilePathsIterator, Symfony\Component\Finder...\FileTypeFilterIterator, Symfony\Component\Finder...lecontentFilterIterator, Symfony\Component\Finder...\FilenameFilterIterator, Symfony\Component\Finder\Iterator\FilterIterator, Symfony\Component\Finder...tiplePcreFilterIterator, Symfony\Component\Finder...ator\PathFilterIterator, Symfony\Component\Finder...ursiveDirectoryIterator, Symfony\Component\Finder...SizeRangeFilterIterator, Symfony\Component\Finder...rator\InnerNameIterator, Symfony\Component\Finder...rator\InnerSizeIterator, Symfony\Component\Finder...rator\InnerTypeIterator, Symfony\Component\Finder\Tests\Iterator\Iterator, Symfony\Component\Finder...or\MockFileListIterator, Symfony\Component\Finder...tiplePcreFilterIterator, TestIterator, TestIterator2, TheSeer\Tokenizer\TokenCollection, TwigTestFoo, Twig\Util\TemplateDirIterator, Twig_Util_TemplateDirIterator, Zend\Cache\Storage\Adapter\ApcIterator, Zend\Cache\Storage\Adapter\ApcuIterator, Zend\Cache\Storage\Adapter\DbaIterator, Zend\Cache\Storage\Adapter\FilesystemIterator, Zend\Cache\Storage\Adapter\KeyListIterator, Zend\Config\Config, Zend\EventManager\ResponseCollection, Zend\Hydrator\Iterator\HydratingArrayIterator, Zend\Hydrator\Iterator\HydratingIteratorIterator, Zend\Stdlib\FastPriorityQueue, Zend\Stdlib\Hydrator\Ite...\HydratingArrayIterator, Zend\Stdlib\Hydrator\Ite...dratingIteratorIterator, Zend\Stdlib\PriorityList, Zend\Stdlib\SplPriorityQueue, Zend\Stdlib\SplQueue, Zend\Stdlib\SplStack, ezcBaseAutoloadOptions, ezcBaseOptions, ezcBaseTestOptions, ezcDocumentBBCodeOptions, ezcDocumentConverterOptions, ezcDocumentDocbookOptions, ezcDocumentDocbookToEzXmlConverterOptions, ezcDocumentDocbookToHtmlConverterOptions, ezcDocumentDocbookToHtmlXsltConverterOptions, ezcDocumentDocbookToOdtConverterOptions, ezcDocumentDocbookToRstConverterOptions, ezcDocumentDocbookToWikiConverterOptions, ezcDocumentEzXmlOptions, ezcDocumentEzXmlToDocbookConverterOptions, ezcDocumentHtmlConverterOptions, ezcDocumentOdtOptions, ezcDocumentOptions, ezcDocumentParserOptions, ezcDocumentPdfFooterOptions, ezcDocumentPdfOptions, ezcDocumentRstOptions, ezcDocumentWikiOptions, ezcDocumentXhtmlOptions, ezcDocumentXmlOptions, ezcDocumentXsltConverterOptions, org\bovigo\vfs\vfsStreamContainerIterator, phpDocumentor\Compiler\Compiler, phpDocumentor\Reflection...y\ClassConstantIterator, phpDocumentor\Reflection...actory\PropertyIterator, phpDocumentor\Transformer\Router\Queue, phpDocumentor\Transformer\Template.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
88
                if ($packageTag instanceof TagDescriptor) {
89
                    $packageName = $packageTag->getDescription();
90
                }
91
            }
92
93
            $subpackageCollection = $element->getTags()->get('subpackage');
94
            if ($subpackageCollection instanceof Collection && $subpackageCollection->count() > 0) {
95
                $subpackageTag = $subpackageCollection->getIterator()->current();
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Traversable as the method current() does only exist in the following implementations of said interface: APCUIterator, AppendIterator, ArrayIterator, Behat\Behat\Gherkin\Spec...ion\LazyFeatureIterator, Behat\Testwork\Specifica...edSpecificationIterator, Behat\Testwork\Specifica...oSpecificationsIterator, Behat\Testwork\Specifica...cificationArrayIterator, CachingIterator, CallbackFilterIterator, CoreTestIterator, DirectoryIterator, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, NoRewindIterator, PHPUnit\Framework\TestSuiteIterator, PHPUnit\Runner\Filter\ExcludeGroupFilterIterator, PHPUnit\Runner\Filter\GroupFilterIterator, PHPUnit\Runner\Filter\IncludeGroupFilterIterator, PHPUnit\Runner\Filter\NameFilterIterator, PHP_Token_Stream, ParentIterator, Phar, PharData, PharIo\Manifest\AuthorCollectionIterator, PharIo\Manifest\AuthorElementCollection, PharIo\Manifest\BundledComponentCollectionIterator, PharIo\Manifest\ComponentElementCollection, PharIo\Manifest\ElementCollection, PharIo\Manifest\ExtElementCollection, PharIo\Manifest\RequirementCollectionIterator, Pimple\ServiceIterator, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SebastianBergmann\CodeCoverage\Node\Iterator, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, Symfony\Component\Finder...or\CustomFilterIterator, Symfony\Component\Finder...DateRangeFilterIterator, Symfony\Component\Finder...epthRangeFilterIterator, Symfony\Component\Finder...DirectoryFilterIterator, Symfony\Component\Finder...rator\FilePathsIterator, Symfony\Component\Finder...\FileTypeFilterIterator, Symfony\Component\Finder...lecontentFilterIterator, Symfony\Component\Finder...\FilenameFilterIterator, Symfony\Component\Finder\Iterator\FilterIterator, Symfony\Component\Finder...tiplePcreFilterIterator, Symfony\Component\Finder...ator\PathFilterIterator, Symfony\Component\Finder...ursiveDirectoryIterator, Symfony\Component\Finder...SizeRangeFilterIterator, Symfony\Component\Finder...rator\InnerNameIterator, Symfony\Component\Finder...rator\InnerSizeIterator, Symfony\Component\Finder...rator\InnerTypeIterator, Symfony\Component\Finder\Tests\Iterator\Iterator, Symfony\Component\Finder...or\MockFileListIterator, Symfony\Component\Finder...tiplePcreFilterIterator, TestIterator, TestIterator2, TheSeer\Tokenizer\TokenCollection, TwigTestFoo, Twig\Util\TemplateDirIterator, Twig_Util_TemplateDirIterator, Zend\Cache\Storage\Adapter\ApcIterator, Zend\Cache\Storage\Adapter\ApcuIterator, Zend\Cache\Storage\Adapter\DbaIterator, Zend\Cache\Storage\Adapter\FilesystemIterator, Zend\Cache\Storage\Adapter\KeyListIterator, Zend\Config\Config, Zend\EventManager\ResponseCollection, Zend\Hydrator\Iterator\HydratingArrayIterator, Zend\Hydrator\Iterator\HydratingIteratorIterator, Zend\Stdlib\FastPriorityQueue, Zend\Stdlib\Hydrator\Ite...\HydratingArrayIterator, Zend\Stdlib\Hydrator\Ite...dratingIteratorIterator, Zend\Stdlib\PriorityList, Zend\Stdlib\SplPriorityQueue, Zend\Stdlib\SplQueue, Zend\Stdlib\SplStack, ezcBaseAutoloadOptions, ezcBaseOptions, ezcBaseTestOptions, ezcDocumentBBCodeOptions, ezcDocumentConverterOptions, ezcDocumentDocbookOptions, ezcDocumentDocbookToEzXmlConverterOptions, ezcDocumentDocbookToHtmlConverterOptions, ezcDocumentDocbookToHtmlXsltConverterOptions, ezcDocumentDocbookToOdtConverterOptions, ezcDocumentDocbookToRstConverterOptions, ezcDocumentDocbookToWikiConverterOptions, ezcDocumentEzXmlOptions, ezcDocumentEzXmlToDocbookConverterOptions, ezcDocumentHtmlConverterOptions, ezcDocumentOdtOptions, ezcDocumentOptions, ezcDocumentParserOptions, ezcDocumentPdfFooterOptions, ezcDocumentPdfOptions, ezcDocumentRstOptions, ezcDocumentWikiOptions, ezcDocumentXhtmlOptions, ezcDocumentXmlOptions, ezcDocumentXsltConverterOptions, org\bovigo\vfs\vfsStreamContainerIterator, phpDocumentor\Compiler\Compiler, phpDocumentor\Reflection...y\ClassConstantIterator, phpDocumentor\Reflection...actory\PropertyIterator, phpDocumentor\Transformer\Router\Queue, phpDocumentor\Transformer\Template.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
96
                if ($subpackageTag instanceof TagDescriptor) {
97
                    $packageName .= '\\' . $subpackageTag->getDescription();
98
                }
99
            }
100
101
            // ensure consistency by trimming the slash prefix and then re-appending it.
102
            $packageIndexName = '\\' . ltrim($packageName, '\\');
103
            if (!isset($project->getIndexes()->packages[$packageIndexName])) {
104
                $this->createPackageDescriptorTree($project, $packageName);
105
            }
106
107
            /** @var PackageDescriptor $package */
108
            $package = $project->getIndexes()->packages[$packageIndexName];
109
110
            // replace textual representation with an object representation
111
            $element->setPackage($package);
112
113
            // add element to package
114
            $getter = 'get'.ucfirst($type);
115
116
            /** @var Collection $collection  */
117
            $collection = $package->$getter();
118
            $collection->add($element);
119
        }
120
    }
121
122
    /**
123
     * Creates a tree of PackageDescriptors based on the provided FQNN (package name).
124
     *
125
     * This method will examine the package name and create a package descriptor for each part of
126
     * the FQNN if it doesn't exist in the packages field of the current package (starting with the root
127
     * Package in the Project Descriptor),
128
     *
129
     * As an intended side effect this method also populates the *elements* index of the ProjectDescriptor with all
130
     * created PackageDescriptors. Each index key is prefixed with a tilde (~) so that it will not conflict with
131
     * other FQSEN's, such as classes or interfaces.
132
     *
133
     * @param ProjectDescriptor $project
134
     * @param string            $packageName A FQNN of the package (and parents) to create.
135
     *
136
     * @see ProjectDescriptor::getPackage() for the root package.
137
     * @see PackageDescriptor::getChildren() for the child packages of a given package.
138
     *
139
     * @return void
140
     */
141
    protected function createPackageDescriptorTree(ProjectDescriptor $project, $packageName)
142
    {
143
        $parts   = explode('\\', ltrim($packageName, '\\'));
144
        $fqnn    = '';
145
146
        // this method does not use recursion to traverse the tree but uses a pointer that will be overridden with the
147
        // next item that is to be traversed (child package) at the end of the loop.
148
149
        /** @var PackageDescriptor $pointer  */
150
        $pointer = $project->getIndexes()->packages['\\'];
151
        foreach ($parts as $part) {
152
            $fqnn .= '\\' . $part;
153
            if ($pointer->getChildren()->get($part)) {
154
                $pointer = $pointer->getChildren()->get($part);
155
                continue;
156
            }
157
158
            // package does not exist, create it
159
            $interimPackageDescriptor = new PackageDescriptor();
160
            $interimPackageDescriptor->setParent($pointer);
161
            $interimPackageDescriptor->setName($part);
162
            $interimPackageDescriptor->setFullyQualifiedStructuralElementName($fqnn);
163
164
            // add to the pointer's list of children
165
            $pointer->getChildren()->set($part ?: 'UNKNOWN', $interimPackageDescriptor);
166
167
            // add to index
168
            $project->getIndexes()->packages[$fqnn] = $interimPackageDescriptor;
169
170
            // move pointer forward
171
            $pointer = $interimPackageDescriptor;
172
        }
173
    }
174
}
175