Completed
Push — develop ( 4b49c4...89d32a )
by Jaap
09:06 queued 05:30
created

src/phpDocumentor/Transformer/Transformer.php (1 issue)

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\Transformer;
13
14
use phpDocumentor\Transformer\Event\WriterInitializationEvent;
15
use phpDocumentor\Transformer\Writer\Initializable;
16
use phpDocumentor\Transformer\Writer\WriterAbstract;
17
use Psr\Log\LogLevel;
18
use phpDocumentor\Compiler\CompilerPassInterface;
19
use phpDocumentor\Descriptor\ProjectDescriptor;
20
use phpDocumentor\Event\DebugEvent;
21
use phpDocumentor\Event\Dispatcher;
22
use phpDocumentor\Event\LogEvent;
23
use phpDocumentor\Transformer\Event\PostTransformEvent;
24
use phpDocumentor\Transformer\Event\PostTransformationEvent;
25
use phpDocumentor\Transformer\Event\PreTransformEvent;
26
use phpDocumentor\Transformer\Event\PreTransformationEvent;
27
28
/**
29
 * Core class responsible for transforming the cache file to a set of artifacts.
30
 */
31
class Transformer implements CompilerPassInterface
32
{
33
    const EVENT_PRE_TRANSFORMATION = 'transformer.transformation.pre';
34
    const EVENT_POST_TRANSFORMATION = 'transformer.transformation.post';
35
    const EVENT_PRE_INITIALIZATION = 'transformer.writer.initialization.pre';
36
    const EVENT_POST_INITIALIZATION = 'transformer.writer.initialization.post';
37
    const EVENT_PRE_TRANSFORM = 'transformer.transform.pre';
38
    const EVENT_POST_TRANSFORM = 'transformer.transform.post';
39
40
    /** @var integer represents the priority in the Compiler queue. */
41
    const COMPILER_PRIORITY = 5000;
42
43
    /** @var string|null $target Target location where to output the artifacts */
44
    protected $target = null;
45
46
    /** @var Template\Collection $templates */
47
    protected $templates;
48
49
    /** @var Writer\Collection|WriterAbstract[] $writers */
50
    protected $writers;
51
52
    /** @var Transformation[] $transformations */
53
    protected $transformations = array();
54
55
    /**
56
     * Wires the template collection and writer collection to this transformer.
57
     *
58
     * @param Template\Collection $templateCollection
59
     * @param Writer\Collection   $writerCollection
60
     */
61 8
    public function __construct(Template\Collection $templateCollection, Writer\Collection $writerCollection)
62
    {
63 8
        $this->templates = $templateCollection;
64 8
        $this->writers   = $writerCollection;
65 8
    }
66
67
    /**
68
     * {@inheritDoc}
69
     */
70 1
    public function getDescription()
71
    {
72 1
        return 'Transform analyzed project into artifacts';
73
    }
74
75
    /**
76
     * Sets the target location where to output the artifacts.
77
     *
78
     * @param string $target The target location where to output the artifacts.
79
     *
80
     * @throws \InvalidArgumentException if the target is not a valid writable
81
     *   directory.
82
     *
83
     * @return void
84
     */
85 3
    public function setTarget($target)
86
    {
87 3
        $path = realpath($target);
88 3
        if (false === $path) {
89 1
            if (@mkdir($target, 0755, true)) {
90
                $path = realpath($target);
91
            } else {
92 1
                throw new \InvalidArgumentException(
93 1
                    'Target directory (' . $target . ') does not exist and could not be created'
94
                );
95
            }
96
        }
97
98 2
        if (!is_dir($path) || !is_writable($path)) {
99 1
            throw new \InvalidArgumentException('Given target (' . $target . ') is not a writable directory');
100
        }
101
102 1
        $this->target = $path;
103 1
    }
104
105
    /**
106
     * Returns the location where to store the artifacts.
107
     *
108
     * @return string
109
     */
110 1
    public function getTarget()
111
    {
112 1
        return $this->target;
113
    }
114
115
    /**
116
     * Returns the list of templates which are going to be adopted.
117
     *
118
     * @return Template\Collection
119
     */
120 2
    public function getTemplates()
121
    {
122 2
        return $this->templates;
123
    }
124
125
    /**
126
     * Transforms the given project into a series of artifacts as provided by the templates.
127
     *
128
     * @param ProjectDescriptor $project
129
     *
130
     * @return void
131
     */
132 1
    public function execute(ProjectDescriptor $project)
133
    {
134 1
        Dispatcher::getInstance()->dispatch(
135 1
            self::EVENT_PRE_TRANSFORM,
136 1
            PreTransformEvent::createInstance($this)->setProject($project)
137
        );
138
139 1
        $transformations = $this->getTemplates()->getTransformations();
140 1
        $this->initializeWriters($project, $transformations);
141 1
        $this->transformProject($project, $transformations);
142
143 1
        Dispatcher::getInstance()->dispatch(self::EVENT_POST_TRANSFORM, PostTransformEvent::createInstance($this));
144
145 1
        $this->log('Finished transformation process');
146 1
    }
147
148
    /**
149
     * Converts a source file name to the name used for generating the end result.
150
     *
151
     * This method strips down the given $name using the following rules:
152
     *
153
     * * if the $name is suffixed with .php then that is removed
154
     * * any occurrence of \ or DIRECTORY_SEPARATOR is replaced with .
155
     * * any dots that the name starts or ends with is removed
156
     * * the result is suffixed with .html
157
     *
158
     * @param string $name Name to convert.
159
     *
160
     * @return string
161
     */
162 1
    public function generateFilename($name)
163
    {
164 1
        if (substr($name, -4) == '.php') {
165 1
            $name = substr($name, 0, -4);
166
        }
167
168 1
        return trim(str_replace(array(DIRECTORY_SEPARATOR, '\\'), '.', trim($name, DIRECTORY_SEPARATOR . '.')), '.')
169 1
            . '.html';
170
    }
171
172
    /**
173
     * Dispatches a logging request.
174
     *
175
     * @param string $message  The message to log.
176
     * @param string $priority The logging priority
177
     *
178
     * @return void
179
     */
180 1
    public function log($message, $priority = LogLevel::INFO)
181
    {
182 1
        Dispatcher::getInstance()->dispatch(
183 1
            'system.log',
184 1
            LogEvent::createInstance($this)
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class phpDocumentor\Event\DebugEvent as the method setPriority() does only exist in the following sub-classes of phpDocumentor\Event\DebugEvent: phpDocumentor\Event\LogEvent. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
185 1
                ->setMessage($message)
186 1
                ->setPriority($priority)
187
        );
188 1
    }
189
190
    /**
191
     * Dispatches a logging request to log a debug message.
192
     *
193
     * @param string $message The message to log.
194
     *
195
     * @return void
196
     */
197
    public function debug($message)
198
    {
199
        Dispatcher::getInstance()->dispatch(
200
            'system.debug',
201
            DebugEvent::createInstance($this)
202
                ->setMessage($message)
203
        );
204
    }
205
206
    /**
207
     * Initializes all writers that are used during this transformation.
208
     *
209
     * @param ProjectDescriptor $project
210
     * @param Transformation[]  $transformations
211
     *
212
     * @return void
213
     */
214 1
    private function initializeWriters(ProjectDescriptor $project, $transformations)
215
    {
216 1
        $isInitialized = array();
217 1
        foreach ($transformations as $transformation) {
218 1
            $writerName = $transformation->getWriter();
219
220 1
            if (in_array($writerName, $isInitialized)) {
221
                continue;
222
            }
223
224 1
            $isInitialized[] = $writerName;
225 1
            $writer = $this->writers[$writerName];
226 1
            $this->initializeWriter($writer, $project);
227
        }
228 1
    }
229
230
    /**
231
     * Initializes the given writer using the provided project meta-data.
232
     *
233
     * This method wil call for the initialization of each writer that supports an initialization routine (as defined by
234
     * the `Initializable` interface).
235
     *
236
     * In addition to this, the following events emitted for each writer that is present in the collected list of
237
     * transformations, even those that do not implement the `Initializable` interface.
238
     *
239
     * Emitted events:
240
     *
241
     * - transformer.writer.initialization.pre, before the initialization of a single writer.
242
     * - transformer.writer.initialization.post, after the initialization of a single writer.
243
     *
244
     * @param WriterAbstract    $writer
245
     * @param ProjectDescriptor $project
246
     *
247
     * @uses Dispatcher to emit the events surrounding an initialization.
248
     *
249
     * @return void
250
     */
251 1
    private function initializeWriter(WriterAbstract $writer, ProjectDescriptor $project)
252
    {
253 1
        $event = WriterInitializationEvent::createInstance($this)->setWriter($writer);
254 1
        Dispatcher::getInstance()->dispatch(self::EVENT_PRE_INITIALIZATION, $event);
255
256 1
        if ($writer instanceof Initializable) {
257
            $writer->initialize($project);
258
        }
259
260 1
        Dispatcher::getInstance()->dispatch(self::EVENT_POST_INITIALIZATION, $event);
261 1
    }
262
263
    /**
264
     * Applies all given transformations to the provided project.
265
     *
266
     * @param ProjectDescriptor $project
267
     * @param Transformation[]  $transformations
268
     *
269
     * @return void
270
     */
271 1
    private function transformProject(ProjectDescriptor $project, $transformations)
272
    {
273 1
        foreach ($transformations as $transformation) {
274 1
            $transformation->setTransformer($this);
275 1
            $this->applyTransformationToProject($transformation, $project);
276
        }
277 1
    }
278
279
    /**
280
     * Applies the given transformation to the provided project.
281
     *
282
     * This method will attempt to find an appropriate writer for the given transformation and invoke that with the
283
     * transformation and project so that an artifact can be generated that matches the intended transformation.
284
     *
285
     * In addition this method will emit the following events:
286
     *
287
     * - transformer.transformation.pre, before the project has been transformed with this transformation.
288
     * - transformer.transformation.post, after the project has been transformed with this transformation
289
     *
290
     * @param Transformation $transformation
291
     * @param ProjectDescriptor $project
292
     *
293
     * @uses Dispatcher to emit the events surrounding a transformation.
294
     *
295
     * @return void
296
     */
297 1
    private function applyTransformationToProject(Transformation $transformation, ProjectDescriptor $project)
298
    {
299 1
        $this->log(
300 1
            sprintf(
301 1
                '  Writer %s %s on %s',
302 1
                $transformation->getWriter(),
303 1
                ($transformation->getQuery() ? ' using query "' . $transformation->getQuery() . '"' : ''),
304 1
                $transformation->getArtifact()
305
            )
306
        );
307
308 1
        $preTransformationEvent = PreTransformationEvent::createInstance($this)->setTransformation($transformation);
309 1
        Dispatcher::getInstance()->dispatch(self::EVENT_PRE_TRANSFORMATION, $preTransformationEvent);
310
311 1
        $writer = $this->writers[$transformation->getWriter()];
312 1
        $writer->transform($project, $transformation);
313
314 1
        $postTransformationEvent = PostTransformationEvent::createInstance($this);
315 1
        Dispatcher::getInstance()->dispatch(self::EVENT_POST_TRANSFORMATION, $postTransformationEvent);
316 1
    }
317
}
318