Completed
Push — master ( f46321...378618 )
by Greg
03:20
created

CollectionBuilder::setVerbosityThreshold()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 4
nop 1
1
<?php
2
namespace Robo\Collection;
3
4
use Robo\Config;
5
use Psr\Log\LogLevel;
6
use Robo\Contract\InflectionInterface;
7
use Robo\Contract\TaskInterface;
8
use Robo\Contract\CompletionInterface;
9
use Robo\Contract\WrappedTaskInterface;
10
use Robo\Task\Simulator;
11
use ReflectionClass;
12
use Robo\Task\BaseTask;
13
use Robo\Contract\BuilderAwareInterface;
14
use Robo\Contract\CommandInterface;
15
use Robo\Contract\VerbosityThresholdInterface;
16
17
/**
18
 * Creates a collection, and adds tasks to it.  The collection builder
19
 * offers a streamlined chained-initialization mechanism for easily
20
 * creating task groups.  Facilities for creating working and temporary
21
 * directories are also provided.
22
 *
23
 * ``` php
24
 * <?php
25
 * $result = $this->collectionBuilder()
26
 *   ->taskFilesystemStack()
27
 *     ->mkdir('g')
28
 *     ->touch('g/g.txt')
29
 *   ->rollback(
30
 *     $this->taskDeleteDir('g')
31
 *   )
32
 *   ->taskFilesystemStack()
33
 *     ->mkdir('g/h')
34
 *     ->touch('g/h/h.txt')
35
 *   ->taskFilesystemStack()
36
 *     ->mkdir('g/h/i/c')
37
 *     ->touch('g/h/i/i.txt')
38
 *   ->run()
39
 * ?>
40
 *
41
 * In the example above, the `taskDeleteDir` will be called if
42
 * ```
43
 */
44
class CollectionBuilder extends BaseTask implements NestedCollectionInterface, WrappedTaskInterface, CommandInterface
45
{
46
    /**
47
     * @var \Robo\Tasks
48
     */
49
    protected $commandFile;
50
51
    /**
52
     * @var CollectionInterface
53
     */
54
    protected $collection;
55
56
    /**
57
     * @var TaskInterface
58
     */
59
    protected $currentTask;
60
61
    /**
62
     * @var bool
63
     */
64
    protected $simulated;
65
66
    /**
67
     * @param \Robo\Tasks $commandFile
68
     */
69
    public function __construct($commandFile)
70
    {
71
        $this->commandFile = $commandFile;
72
    }
73
74
    /**
75
     * @param bool $simulated
76
     *
77
     * @return $this
78
     */
79
    public function simulated($simulated = true)
80
    {
81
        $this->simulated = $simulated;
82
        return $this;
83
    }
84
85
    /**
86
     * @return bool
87
     */
88
    public function isSimulated()
89
    {
90
        if (!isset($this->simulated)) {
91
            $this->simulated = $this->getConfig()->get(Config::SIMULATE);
92
        }
93
        return $this->simulated;
94
    }
95
96
    /**
97
     * Create a temporary directory to work in. When the collection
98
     * completes or rolls back, the temporary directory will be deleted.
99
     * Returns the path to the location where the directory will be
100
     * created.
101
     *
102
     * @param string $prefix
103
     * @param string $base
104
     * @param bool $includeRandomPart
105
     *
106
     * @return string
107
     */
108
    public function tmpDir($prefix = 'tmp', $base = '', $includeRandomPart = true)
109
    {
110
        // n.b. Any task that the builder is asked to create is
111
        // automatically added to the builder's collection, and
112
        // wrapped in the builder object. Therefore, the result
113
        // of any call to `taskFoo()` from within the builder will
114
        // always be `$this`.
115
        return $this->taskTmpDir($prefix, $base, $includeRandomPart)->getPath();
0 ignored issues
show
Bug introduced by
The method taskTmpDir() does not exist on Robo\Collection\CollectionBuilder. Did you maybe mean tmpDir()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
116
    }
117
118
    /**
119
     * Create a working directory to hold results. A temporary directory
120
     * is first created to hold the intermediate results.  After the
121
     * builder finishes, the work directory is moved into its final location;
122
     * any results already in place will be moved out of the way and
123
     * then deleted.
124
     *
125
     * @param string $finalDestination The path where the working directory
126
     *   will be moved once the task collection completes.
127
     *
128
     * @return string
129
     */
130
    public function workDir($finalDestination)
131
    {
132
        // Creating the work dir task in this context adds it to our task collection.
133
        return $this->taskWorkDir($finalDestination)->getPath();
0 ignored issues
show
Bug introduced by
The method taskWorkDir() does not exist on Robo\Collection\CollectionBuilder. Did you maybe mean workDir()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
134
    }
135
136
    public function addTask(TaskInterface $task)
137
    {
138
        $this->getCollection()->add($task);
139
        return $this;
140
    }
141
142
    public function addCode(callable $code)
143
    {
144
        $this->getCollection()->addCode($code);
145
        return $this;
146
    }
147
148
    /**
149
     * Add a list of tasks to our task collection.
150
     *
151
     * @param TaskInterface[] $tasks
152
     *   An array of tasks to run with rollback protection
153
     *
154
     * @return $this
155
     */
156
    public function addTaskList(array $tasks)
157
    {
158
        $this->getCollection()->addTaskList($tasks);
159
        return $this;
160
    }
161
162
    public function rollback(TaskInterface $task)
163
    {
164
        // Ensure that we have a collection if we are going to add
165
        // a rollback function.
166
        $this->getCollection()->rollback($task);
167
        return $this;
168
    }
169
170
    public function rollbackCode(callable $rollbackCode)
171
    {
172
        $this->getCollection()->rollbackCode($rollbackCode);
173
        return $this;
174
    }
175
176
    public function completion(TaskInterface $task)
177
    {
178
        $this->getCollection()->completion($task);
179
        return $this;
180
    }
181
182
    public function completionCode(callable $completionCode)
183
    {
184
        $this->getCollection()->completionCode($completionCode);
185
        return $this;
186
    }
187
188
    /**
189
     * @param string $text
190
     * @param array $context
191
     * @param string $level
192
     *
193
     * @return $this
194
     */
195
    public function progressMessage($text, $context = [], $level = LogLevel::NOTICE)
196
    {
197
        $this->getCollection()->progressMessage($text, $context, $level);
198
        return $this;
199
    }
200
201
    /**
202
     * @param \Robo\Collection\NestedCollectionInterface $parentCollection
203
     *
204
     * @return $this
205
     */
206
    public function setParentCollection(NestedCollectionInterface $parentCollection)
207
    {
208
        $this->getCollection()->setParentCollection($parentCollection);
209
        return $this;
210
    }
211
212
    /**
213
     * Called by the factory method of each task; adds the current
214
     * task to the task builder.
215
     *
216
     * TODO: protected
217
     *
218
     * @param TaskInterface $task
219
     *
220
     * @return $this
221
     */
222
    public function addTaskToCollection($task)
223
    {
224
        // Postpone creation of the collection until the second time
225
        // we are called. At that time, $this->currentTask will already
226
        // be populated.  We call 'getCollection()' so that it will
227
        // create the collection and add the current task to it.
228
        // Note, however, that if our only tasks implements NestedCollectionInterface,
229
        // then we should force this builder to use a collection.
230
        if (!$this->collection && (isset($this->currentTask) || ($task instanceof NestedCollectionInterface))) {
231
            $this->getCollection();
232
        }
233
        $this->currentTask = $task;
234
        if ($this->collection) {
235
            $this->collection->add($task);
236
        }
237
        return $this;
238
    }
239
240
    public function setVerbosityThreshold($verbosityThreshold)
241
    {
242
        $currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
243
        if ($currentTask) {
244
            $currentTask->setVerbosityThreshold($verbosityThreshold);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Robo\Contract\TaskInterface as the method setVerbosityThreshold() does only exist in the following implementations of said interface: ExceptionTask, Robo\Collection\Collection, Robo\Collection\CollectionBuilder, Robo\Collection\CompletionWrapper, Robo\Collection\TaskForEach, Robo\Task\ApiGen\ApiGen, Robo\Task\Archive\Extract, Robo\Task\Archive\Pack, Robo\Task\Assets\CssPreprocessor, Robo\Task\Assets\ImageMinify, Robo\Task\Assets\Less, Robo\Task\Assets\Minify, Robo\Task\Assets\Scss, Robo\Task\BaseTask, Robo\Task\Base\Exec, Robo\Task\Base\ExecStack, Robo\Task\Base\ParallelExec, Robo\Task\Base\SymfonyCommand, Robo\Task\Base\Watch, Robo\Task\Bower\Base, Robo\Task\Bower\Install, Robo\Task\Bower\Update, Robo\Task\CommandStack, Robo\Task\Composer\Base, Robo\Task\Composer\DumpAutoload, Robo\Task\Composer\Install, Robo\Task\Composer\Remove, Robo\Task\Composer\Update, Robo\Task\Composer\Validate, Robo\Task\Development\Changelog, Robo\Task\Development\GenerateMarkdownDoc, Robo\Task\Development\GenerateTask, Robo\Task\Development\GitHub, Robo\Task\Development\GitHubRelease, Robo\Task\Development\OpenBrowser, Robo\Task\Development\PackPhar, Robo\Task\Development\PhpServer, Robo\Task\Docker\Base, Robo\Task\Docker\Build, Robo\Task\Docker\Commit, Robo\Task\Docker\Exec, Robo\Task\Docker\Pull, Robo\Task\Docker\Remove, Robo\Task\Docker\Run, Robo\Task\Docker\Start, Robo\Task\Docker\Stop, Robo\Task\File\Concat, Robo\Task\File\Replace, Robo\Task\File\TmpFile, Robo\Task\File\Write, Robo\Task\Filesystem\BaseDir, Robo\Task\Filesystem\CleanDir, Robo\Task\Filesystem\CopyDir, Robo\Task\Filesystem\DeleteDir, Robo\Task\Filesystem\FilesystemStack, Robo\Task\Filesystem\FlattenDir, Robo\Task\Filesystem\MirrorDir, Robo\Task\Filesystem\TmpDir, Robo\Task\Filesystem\WorkDir, Robo\Task\Gulp\Base, Robo\Task\Gulp\Run, Robo\Task\Npm\Base, Robo\Task\Npm\Install, Robo\Task\Npm\Update, Robo\Task\Remote\Rsync, Robo\Task\Remote\Ssh, Robo\Task\Simulator, Robo\Task\StackBasedTask, Robo\Task\Testing\Atoum, Robo\Task\Testing\Behat, Robo\Task\Testing\Codecept, Robo\Task\Testing\PHPUnit, Robo\Task\Testing\Phpspec, Robo\Task\Vcs\GitStack, Robo\Task\Vcs\HgStack, Robo\Task\Vcs\SvnStack, TestedRoboTask, unit\CollectionTestTask, unit\ConfigurationTestTaskA, unit\ConfigurationTestTaskB, unit\CountingTask.

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...
245
            return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Robo\Collection\CollectionBuilder) is incompatible with the return type of the parent method Robo\Task\BaseTask::setVerbosityThreshold of type Robo\Common\VerbosityThresholdTrait.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
246
        }
247
        parent::setVerbosityThreshold($verbosityThreshold);
248
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Robo\Collection\CollectionBuilder) is incompatible with the return type of the parent method Robo\Task\BaseTask::setVerbosityThreshold of type Robo\Common\VerbosityThresholdTrait.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
249
    }
250
251
252
    /**
253
     * Return the current task for this collection builder.
254
     * TODO: Not needed?
255
     *
256
     * @return \Robo\Contract\TaskInterface
257
     */
258
    public function getCollectionBuilderCurrentTask()
259
    {
260
        return $this->currentTask;
261
    }
262
263
    /**
264
     * Create a new builder with its own task collection
265
     *
266
     * @return CollectionBuilder
267
     */
268
    public function newBuilder()
269
    {
270
        $collectionBuilder = new self($this->commandFile);
271
        $collectionBuilder->inflect($this);
272
        $collectionBuilder->simulated($this->isSimulated());
273
        $collectionBuilder->setVerbosityThreshold($this->verbosityThreshold());
274
275
        return $collectionBuilder;
276
    }
277
278
    /**
279
     * Calling the task builder with methods of the current
280
     * task calls through to that method of the task.
281
     *
282
     * There is extra complexity in this function that could be
283
     * simplified if we attached the 'LoadAllTasks' and custom tasks
284
     * to the collection builder instead of the RoboFile.  While that
285
     * change would be a better design overall, it would require that
286
     * the user do a lot more work to set up and use custom tasks.
287
     * We therefore take on some additional complexity here in order
288
     * to allow users to maintain their tasks in their RoboFile, which
289
     * is much more convenient.
290
     *
291
     * Calls to $this->collectionBuilder()->taskFoo() cannot be made
292
     * directly because all of the task methods are protected.  These
293
     * calls will therefore end up here.  If the method name begins
294
     * with 'task', then it is eligible to be used with the builder.
295
     *
296
     * When we call getBuiltTask, below, it will use the builder attached
297
     * to the commandfile to build the task. However, this is not what we
298
     * want:  the task needs to be built from THIS collection builder, so that
299
     * it will be affected by whatever state is active in this builder.
300
     * To do this, we have two choices: 1) save and restore the builder
301
     * in the commandfile, or 2) clone the commandfile and set this builder
302
     * on the copy. 1) is vulnerable to failure in multithreaded environments
303
     * (currently not supported), while 2) might cause confusion if there
304
     * is shared state maintained in the commandfile, which is in the
305
     * domain of the user.
306
     *
307
     * Note that even though we are setting up the commandFile to
308
     * use this builder, getBuiltTask always creates a new builder
309
     * (which is constructed using all of the settings from the
310
     * commandFile's builder), and the new task is added to that.
311
     * We therefore need to transfer the newly built task into this
312
     * builder. The temporary builder is discarded.
313
     *
314
     * @param string $fn
315
     * @param array $args
316
     *
317
     * @return $this|mixed
318
     */
319
    public function __call($fn, $args)
320
    {
321
        if (preg_match('#^task[A-Z]#', $fn) && (method_exists($this->commandFile, 'getBuiltTask'))) {
322
            $saveBuilder = $this->commandFile->getBuilder();
323
            $this->commandFile->setBuilder($this);
324
            $temporaryBuilder = $this->commandFile->getBuiltTask($fn, $args);
325
            $this->commandFile->setBuilder($saveBuilder);
326
            if (!$temporaryBuilder) {
327
                throw new \BadMethodCallException("No such method $fn: task does not exist in " . get_class($this->commandFile));
328
            }
329
            $temporaryBuilder->getCollection()->transferTasks($this);
330
            return $this;
331
        }
332
        if (!isset($this->currentTask)) {
333
            throw new \BadMethodCallException("No such method $fn: current task undefined in collection builder.");
334
        }
335
        // If the method called is a method of the current task,
336
        // then call through to the current task's setter method.
337
        $result = call_user_func_array([$this->currentTask, $fn], $args);
338
339
        // If something other than a setter method is called, then return its result.
340
        $currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
341
        if (isset($result) && ($result !== $currentTask)) {
342
            return $result;
343
        }
344
345
        return $this;
346
    }
347
348
    /**
349
     * Construct the desired task and add it to this builder.
350
     *
351
     * @param string|object $name
352
     * @param array $args
353
     *
354
     * @return \Robo\Collection\CollectionBuilder
355
     */
356
    public function build($name, $args)
357
    {
358
        $reflection = new ReflectionClass($name);
359
        $task = $reflection->newInstanceArgs($args);
360
        if (!$task) {
361
            throw new RuntimeException("Can not construct task $name");
362
        }
363
        $task = $this->fixTask($task, $args);
364
        return $this->addTaskToCollection($task);
365
    }
366
367
    /**
368
     * @param InflectionInterface $task
369
     * @param array $args
370
     *
371
     * @return \Robo\Collection\CompletionWrapper|\Robo\Task\Simulator
372
     */
373
    protected function fixTask($task, $args)
374
    {
375
        if ($task instanceof InflectionInterface) {
376
            $task->inflect($this);
377
        }
378
        if ($task instanceof BuilderAwareInterface) {
379
            $task->setBuilder($this);
380
        }
381
        if ($task instanceof VerbosityThresholdInterface) {
382
            $task->setVerbosityThreshold($this->verbosityThreshold());
383
        }
384
385
        // Do not wrap our wrappers.
386
        if ($task instanceof CompletionWrapper || $task instanceof Simulator) {
387
            return $task;
388
        }
389
390
        // Remember whether or not this is a task before
391
        // it gets wrapped in any decorator.
392
        $isTask = $task instanceof TaskInterface;
393
        $isCollection = $task instanceof NestedCollectionInterface;
394
395
        // If the task implements CompletionInterface, ensure
396
        // that its 'complete' method is called when the application
397
        // terminates -- but only if its 'run' method is called
398
        // first.  If the task is added to a collection, then the
399
        // task will be unwrapped via its `original` method, and
400
        // it will be re-wrapped with a new completion wrapper for
401
        // its new collection.
402
        if ($task instanceof CompletionInterface) {
403
            $task = new CompletionWrapper(Temporary::getCollection(), $task);
404
        }
405
406
        // If we are in simulated mode, then wrap any task in
407
        // a TaskSimulator.
408
        if ($isTask && !$isCollection && ($this->isSimulated())) {
409
            $task = new \Robo\Task\Simulator($task, $args);
0 ignored issues
show
Documentation introduced by
$task is of type object<Robo\Contract\InflectionInterface>, but the function expects a object<Robo\Contract\TaskInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
410
            $task->inflect($this);
411
        }
412
413
        return $task;
414
    }
415
416
    /**
417
     * When we run the collection builder, run everything in the collection.
418
     *
419
     * @return \Robo\Result
420
     */
421
    public function run()
422
    {
423
        $this->startTimer();
424
        $result = $this->runTasks();
425
        $this->stopTimer();
426
        $result['time'] = $this->getExecutionTime();
427
        return $result;
428
    }
429
430
    /**
431
     * If there is a single task, run it; if there is a collection, run
432
     * all of its tasks.
433
     *
434
     * @return \Robo\Result
435
     */
436
    protected function runTasks()
437
    {
438
        if (!$this->collection && $this->currentTask) {
439
            return $this->currentTask->run();
440
        }
441
        return $this->getCollection()->run();
442
    }
443
444
    /**
445
     * @return string
446
     */
447
    public function getCommand()
448
    {
449
        if (!$this->collection && $this->currentTask) {
450
            $task = $this->currentTask;
451
            $task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
452
            if ($task instanceof CommandInterface) {
453
                return $task->getCommand();
454
            }
455
        }
456
457
        return $this->getCollection()->getCommand();
458
    }
459
460
    /**
461
     * @return \Robo\Collection\Collection
462
     */
463
    public function original()
464
    {
465
        return $this->getCollection();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getCollection(); (Robo\Collection\CollectionInterface) is incompatible with the return type declared by the interface Robo\Contract\WrappedTaskInterface::original of type Robo\Contract\TaskInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
466
    }
467
468
    /**
469
     * Return the collection of tasks associated with this builder.
470
     *
471
     * @return CollectionInterface
472
     */
473
    public function getCollection()
474
    {
475
        if (!isset($this->collection)) {
476
            $this->collection = new Collection();
477
            $this->collection->inflect($this);
478
            $this->collection->setProgressBarAutoDisplayInterval($this->getConfig()->get(Config::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL));
479
480
            if (isset($this->currentTask)) {
481
                $this->collection->add($this->currentTask);
482
            }
483
        }
484
        return $this->collection;
485
    }
486
}
487