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

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

Labels
Severity

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