Completed
Push — develop ( fbdd82...317691 )
by Mike
09:29
created

Renderer/Template/Action/GraphHandler.php (1 issue)

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.4
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\Application\Renderer\Template\Action;
13
14
use phpDocumentor\GraphViz\Edge;
15
use phpDocumentor\GraphViz\Graph as GraphVizGraph;
16
use phpDocumentor\GraphViz\Node;
17
use phpDocumentor\DomainModel\Renderer\Template\Action;
18
use phpDocumentor\DomainModel\Renderer\Template\ActionHandler;
19
20
class GraphHandler implements ActionHandler
21
{
22
    /** @var string Name of the font to use to display the node labels with */
23
    protected $nodeFont = 'Courier';
24
25
    /** @var Node[] a cache where nodes for classes, interfaces and traits are stored for reference */
26
    protected $nodeCache = array();
27
28
    /** @var Analyzer */
29
    private $analyzer;
30
31
    public function __construct(Analyzer $analyzer)
32
    {
33
        $this->analyzer = $analyzer;
34
    }
35
36
    /**
37
     * Executes the activities that this Action represents.
38
     *
39
     * @param Action|Graph $action
40
     *
41
     * @return void
42
     */
43
    public function __invoke(Action $action)
44
    {
45
        $project = $this->analyzer->getProjectDescriptor();
46
        try {
47
            $this->checkIfGraphVizIsInstalled();
48
        } catch (\Exception $e) {
49
            echo $e->getMessage();
50
51
            return;
52
        }
53
54
        $graph = GraphVizGraph::create()
55
            ->setRankSep('1.0')
56
            ->setCenter('true')
57
            ->setRank('source')
58
            ->setRankDir('RL')
59
            ->setSplines('true')
60
            ->setConcentrate('true');
61
62
        $this->buildNamespaceTree($graph, $project->getNamespace());
63
64
        $classes    = $project->getIndexes()->get('classes', new Collection())->getAll();
65
        $interfaces = $project->getIndexes()->get('interfaces', new Collection())->getAll();
66
        $traits     = $project->getIndexes()->get('traits', new Collection())->getAll();
67
68
        /** @var ClassDescriptor[]|InterfaceDescriptor[]|TraitDescriptor[] $containers  */
69
        $containers = array_merge($classes, $interfaces, $traits);
70
71
        foreach ($containers as $container) {
72
            $from_name = $container->getFullyQualifiedStructuralElementName();
73
74
            $parents     = array();
75
            $implemented = array();
76
            if ($container instanceof ClassDescriptor) {
77
                if ($container->getParent()) {
78
                    $parents[] = $container->getParent();
79
                }
80
                $implemented = $container->getInterfaces()->getAll();
81
            }
82
            if ($container instanceof InterfaceDescriptor) {
83
                $parents = $container->getParent()->getAll();
84
            }
85
86
            /** @var string|ClassDescriptor|InterfaceDescriptor $parent */
87
            foreach ($parents as $parent) {
88
                $edge = $this->createEdge($graph, $from_name, $parent);
89
                $edge->setArrowHead('empty');
90
                $graph->link($edge);
91
            }
92
93
            /** @var string|ClassDescriptor|InterfaceDescriptor $parent */
94
            foreach ($implemented as $parent) {
95
                $edge = $this->createEdge($graph, $from_name, $parent);
96
                $edge->setStyle('dotted');
97
                $edge->setArrowHead('empty');
98
                $graph->link($edge);
99
            }
100
        }
101
102
        $destination = $action->getRenderContext()->getDestination() . '/' . $action->getDestination();
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface phpDocumentor\DomainModel\Renderer\Template\Action as the method getRenderContext() does only exist in the following implementations of said interface: phpDocumentor\Applicatio...plate\Action\AppendFile, phpDocumentor\Applicatio...plate\Action\Checkstyle, phpDocumentor\Applicatio...emplate\Action\CopyFile, phpDocumentor\Applicatio...r\Template\Action\Graph, phpDocumentor\Applicatio...r\Template\Action\Jsonp, phpDocumentor\Applicatio...plate\Action\Sourcecode, phpDocumentor\Applicatio...er\Template\Action\Twig, phpDocumentor\Applicatio...rer\Template\Action\Xml, phpDocumentor\Applicatio...rer\Template\Action\Xsl.

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...
103
        if (! file_exists(dirname($destination))) {
104
            @mkdir(dirname($destination), 0777, true);
105
        }
106
        $graph->export('svg', $destination);
107
    }
108
109
    /**
110
     * Creates a GraphViz Edge between two nodes.
111
     *
112
     * @param Graph  $graph
113
     * @param string $from_name
114
     * @param string|ClassDescriptor|InterfaceDescriptor|TraitDescriptor $to
115
     *
116
     * @return Edge
117
     */
118
    protected function createEdge($graph, $from_name, $to)
119
    {
120
        $to_name = !is_string($to) ? $to->getFullyQualifiedStructuralElementName() : $to;
121
122
        if (!isset($this->nodeCache[$from_name])) {
123
            $this->nodeCache[$from_name] = $this->createEmptyNode($from_name, $graph);
124
        }
125
        if (!isset($this->nodeCache[$to_name])) {
126
            $this->nodeCache[$to_name] = $this->createEmptyNode($to_name, $graph);
127
        }
128
129
        return Edge::create($this->nodeCache[$from_name], $this->nodeCache[$to_name]);
130
    }
131
132
    /**
133
     * @param string $name
134
     * @param Graph $graph
135
     *
136
     * @return Node
137
     */
138
    protected function createEmptyNode($name, $graph)
139
    {
140
        $node = Node::create($name);
141
        $node->setFontColor('gray');
142
        $node->setLabel($name);
143
        $graph->setNode($node);
144
145
        return $node;
146
    }
147
148
    /**
149
     * Builds a tree of namespace subgraphs with their classes associated.
150
     *
151
     * @param GraphVizGraph       $graph
152
     * @param NamespaceDescriptor $namespace
153
     *
154
     * @return void
155
     */
156
    protected function buildNamespaceTree(GraphVizGraph $graph, NamespaceDescriptor $namespace)
157
    {
158
        $full_namespace_name = $namespace->getFullyQualifiedStructuralElementName();
159
        if ($full_namespace_name == '\\') {
160
            $full_namespace_name = 'Global';
161
        }
162
163
        $sub_graph = GraphVizGraph::create('cluster_' . $full_namespace_name)
164
            ->setLabel($namespace->getName() == '\\' ? 'Global' : $namespace->getName())
165
            ->setStyle('rounded')
166
            ->setColor('gray')
167
            ->setFontColor('gray')
168
            ->setFontSize('11')
169
            ->setRankDir('LR');
170
171
        $elements = array_merge(
172
            $namespace->getClasses()->getAll(),
173
            $namespace->getInterfaces()->getAll(),
174
            $namespace->getTraits()->getAll()
175
        );
176
177
        /** @var ClassDescriptor|InterfaceDescriptor|TraitDescriptor $sub_element */
178
        foreach ($elements as $sub_element) {
179
            $node = Node::create($sub_element->getFullyQualifiedStructuralElementName(), $sub_element->getName())
180
                ->setShape('box')
181
                ->setFontName($this->nodeFont)
182
                ->setFontSize('11');
183
184
            if ($sub_element instanceof ClassDescriptor && $sub_element->isAbstract()) {
185
                $node->setLabel('<«abstract»<br/>' . $sub_element->getName(). '>');
186
            }
187
188
            //$full_name = $sub_element->getFullyQualifiedStructuralElementName();
189
            //$node->setURL($this->class_paths[$full_name]);
190
            //$node->setTarget('_parent');
191
192
            $this->nodeCache[$sub_element->getFullyQualifiedStructuralElementName()] = $node;
193
            $sub_graph->setNode($node);
194
        }
195
196
        foreach ($namespace->getChildren()->getAll() as $element) {
197
            $this->buildNamespaceTree($sub_graph, $element);
198
        }
199
200
        $graph->addGraph($sub_graph);
201
    }
202
203
    /**
204
     * Checks whether GraphViz is installed and throws an Exception otherwise.
205
     *
206
     * @throws \RuntimeException if graphviz is not found.
207
     *
208
     * @return void
209
     */
210
    protected function checkIfGraphVizIsInstalled()
211
    {
212
        // NOTE: the -V flag sends output using STDERR and STDOUT
213
        exec('dot -V 2>&1', $output, $error);
214
        if ($error != 0) {
215
            throw new \RuntimeException(
216
                'Unable to find the `dot` command of the GraphViz package. '
217
                . 'Is GraphViz correctly installed and present in your path?'
218
            );
219
        }
220
    }
221
}
222