Completed
Pull Request — master (#533)
by Greg
08:31
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
16
/**
17
 * Creates a collection, and adds tasks to it.  The collection builder
18
 * offers a streamlined chained-initialization mechanism for easily
19
 * creating task groups.  Facilities for creating working and temporary
20
 * directories are also provided.
21
 *
22
 * ``` php
23
 * <?php
24
 * $result = $this->collectionBuilder()
25
 *   ->taskFilesystemStack()
26
 *     ->mkdir('g')
27
 *     ->touch('g/g.txt')
28
 *   ->rollback(
29
 *     $this->taskDeleteDir('g')
30
 *   )
31
 *   ->taskFilesystemStack()
32
 *     ->mkdir('g/h')
33
 *     ->touch('g/h/h.txt')
34
 *   ->taskFilesystemStack()
35
 *     ->mkdir('g/h/i/c')
36
 *     ->touch('g/h/i/i.txt')
37
 *   ->run()
38
 * ?>
39
 *
40
 * In the example above, the `taskDeleteDir` will be called if
41
 * ```
42
 */
43
class CollectionBuilder extends BaseTask implements NestedCollectionInterface, WrappedTaskInterface, CommandInterface
44
{
45
    /**
46
     * @var \Robo\Tasks
47
     */
48
    protected $commandFile;
49
50
    /**
51
     * @var CollectionInterface
52
     */
53
    protected $collection;
54
55
    /**
56
     * @var TaskInterface
57
     */
58
    protected $currentTask;
59
60
    /**
61
     * @var bool
62
     */
63
    protected $simulated;
64
65
    /**
66
     * @param \Robo\Tasks $commandFile
67
     */
68
    public function __construct($commandFile)
69
    {
70
        $this->commandFile = $commandFile;
71
    }
72
73
    /**
74
     * @param bool $simulated
75
     *
76
     * @return $this
77
     */
78
    public function simulated($simulated = true)
79
    {
80
        $this->simulated = $simulated;
81
        return $this;
82
    }
83
84
    /**
85
     * @return bool
86
     */
87
    public function isSimulated()
88
    {
89
        if (!isset($this->simulated)) {
90
            $this->simulated = $this->getConfig()->get(Config::SIMULATE);
91
        }
92
        return $this->simulated;
93
    }
94
95
    /**
96
     * Create a temporary directory to work in. When the collection
97
     * completes or rolls back, the temporary directory will be deleted.
98
     * Returns the path to the location where the directory will be
99
     * created.
100
     *
101
     * @param string $prefix
102
     * @param string $base
103
     * @param bool $includeRandomPart
104
     *
105
     * @return string
106
     */
107
    public function tmpDir($prefix = 'tmp', $base = '', $includeRandomPart = true)
108
    {
109
        // n.b. Any task that the builder is asked to create is
110
        // automatically added to the builder's collection, and
111
        // wrapped in the builder object. Therefore, the result
112
        // of any call to `taskFoo()` from within the builder will
113
        // always be `$this`.
114
        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...
115
    }
116
117
    /**
118
     * Create a working directory to hold results. A temporary directory
119
     * is first created to hold the intermediate results.  After the
120
     * builder finishes, the work directory is moved into its final location;
121
     * any results already in place will be moved out of the way and
122
     * then deleted.
123
     *
124
     * @param string $finalDestination The path where the working directory
125
     *   will be moved once the task collection completes.
126
     *
127
     * @return string
128
     */
129
    public function workDir($finalDestination)
130
    {
131
        // Creating the work dir task in this context adds it to our task collection.
132
        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...
133
    }
134
135
    public function addTask(TaskInterface $task)
136
    {
137
        $this->getCollection()->add($task);
138
        return $this;
139
    }
140
141
    public function addCode(callable $code)
142
    {
143
        $this->getCollection()->addCode($code);
144
        return $this;
145
    }
146
147
    /**
148
     * Add a list of tasks to our task collection.
149
     *
150
     * @param TaskInterface[] $tasks
151
     *   An array of tasks to run with rollback protection
152
     *
153
     * @return $this
154
     */
155
    public function addTaskList(array $tasks)
156
    {
157
        $this->getCollection()->addTaskList($tasks);
158
        return $this;
159
    }
160
161
    public function rollback(TaskInterface $task)
162
    {
163
        // Ensure that we have a collection if we are going to add
164
        // a rollback function.
165
        $this->getCollection()->rollback($task);
166
        return $this;
167
    }
168
169
    public function rollbackCode(callable $rollbackCode)
170
    {
171
        $this->getCollection()->rollbackCode($rollbackCode);
172
        return $this;
173
    }
174
175
    public function completion(TaskInterface $task)
176
    {
177
        $this->getCollection()->completion($task);
178
        return $this;
179
    }
180
181
    public function completionCode(callable $completionCode)
182
    {
183
        $this->getCollection()->completionCode($completionCode);
184
        return $this;
185
    }
186
187
    /**
188
     * @param string $text
189
     * @param array $context
190
     * @param string $level
191
     *
192
     * @return $this
193
     */
194
    public function progressMessage($text, $context = [], $level = LogLevel::NOTICE)
195
    {
196
        $this->getCollection()->progressMessage($text, $context, $level);
197
        return $this;
198
    }
199
200
    /**
201
     * @param \Robo\Collection\NestedCollectionInterface $parentCollection
202
     *
203
     * @return $this
204
     */
205
    public function setParentCollection(NestedCollectionInterface $parentCollection)
206
    {
207
        $this->getCollection()->setParentCollection($parentCollection);
208
        return $this;
209
    }
210
211
    /**
212
     * Called by the factory method of each task; adds the current
213
     * task to the task builder.
214
     *
215
     * TODO: protected
216
     *
217
     * @param TaskInterface $task
218
     *
219
     * @return $this
220
     */
221
    public function addTaskToCollection($task)
222
    {
223
        // Postpone creation of the collection until the second time
224
        // we are called. At that time, $this->currentTask will already
225
        // be populated.  We call 'getCollection()' so that it will
226
        // create the collection and add the current task to it.
227
        // Note, however, that if our only tasks implements NestedCollectionInterface,
228
        // then we should force this builder to use a collection.
229
        if (!$this->collection && (isset($this->currentTask) || ($task instanceof NestedCollectionInterface))) {
230
            $this->getCollection();
231
        }
232
        $this->currentTask = $task;
233
        if ($this->collection) {
234
            $this->collection->add($task);
235
        }
236
        return $this;
237
    }
238
239
    public function setVerbosityThreshold($verbosityThreshold)
240
    {
241
        $currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
242
        if ($currentTask) {
243
            $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...
244
            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\TaskIO.

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...
245
        }
246
        parent::setVerbosityThreshold($verbosityThreshold);
247
        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\TaskIO.

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...
248
    }
249
250
251
    /**
252
     * Return the current task for this collection builder.
253
     * TODO: Not needed?
254
     *
255
     * @return \Robo\Contract\TaskInterface
256
     */
257
    public function getCollectionBuilderCurrentTask()
258
    {
259
        return $this->currentTask;
260
    }
261
262
    /**
263
     * Create a new builder with its own task collection
264
     *
265
     * @return CollectionBuilder
266
     */
267
    public function newBuilder()
268
    {
269
        $collectionBuilder = new self($this->commandFile);
270
        $collectionBuilder->inflect($this);
271
        $collectionBuilder->simulated($this->isSimulated());
272
        $collectionBuilder->setVerbosityThreshold($this->verbosityThreshold());
273
274
        return $collectionBuilder;
275
    }
276
277
    /**
278
     * Calling the task builder with methods of the current
279
     * task calls through to that method of the task.
280
     *
281
     * There is extra complexity in this function that could be
282
     * simplified if we attached the 'LoadAllTasks' and custom tasks
283
     * to the collection builder instead of the RoboFile.  While that
284
     * change would be a better design overall, it would require that
285
     * the user do a lot more work to set up and use custom tasks.
286
     * We therefore take on some additional complexity here in order
287
     * to allow users to maintain their tasks in their RoboFile, which
288
     * is much more convenient.
289
     *
290
     * Calls to $this->collectionBuilder()->taskFoo() cannot be made
291
     * directly because all of the task methods are protected.  These
292
     * calls will therefore end up here.  If the method name begins
293
     * with 'task', then it is eligible to be used with the builder.
294
     *
295
     * When we call getBuiltTask, below, it will use the builder attached
296
     * to the commandfile to build the task. However, this is not what we
297
     * want:  the task needs to be built from THIS collection builder, so that
298
     * it will be affected by whatever state is active in this builder.
299
     * To do this, we have two choices: 1) save and restore the builder
300
     * in the commandfile, or 2) clone the commandfile and set this builder
301
     * on the copy. 1) is vulnerable to failure in multithreaded environments
302
     * (currently not supported), while 2) might cause confusion if there
303
     * is shared state maintained in the commandfile, which is in the
304
     * domain of the user.
305
     *
306
     * Note that even though we are setting up the commandFile to
307
     * use this builder, getBuiltTask always creates a new builder
308
     * (which is constructed using all of the settings from the
309
     * commandFile's builder), and the new task is added to that.
310
     * We therefore need to transfer the newly built task into this
311
     * builder. The temporary builder is discarded.
312
     *
313
     * @param string $fn
314
     * @param array $args
315
     *
316
     * @return $this|mixed
317
     */
318
    public function __call($fn, $args)
319
    {
320
        if (preg_match('#^task[A-Z]#', $fn) && (method_exists($this->commandFile, 'getBuiltTask'))) {
321
            $saveBuilder = $this->commandFile->getBuilder();
322
            $this->commandFile->setBuilder($this);
323
            $temporaryBuilder = $this->commandFile->getBuiltTask($fn, $args);
324
            $this->commandFile->setBuilder($saveBuilder);
325
            if (!$temporaryBuilder) {
326
                throw new \BadMethodCallException("No such method $fn: task does not exist in " . get_class($this->commandFile));
327
            }
328
            $temporaryBuilder->getCollection()->transferTasks($this);
329
            return $this;
330
        }
331
        if (!isset($this->currentTask)) {
332
            throw new \BadMethodCallException("No such method $fn: current task undefined in collection builder.");
333
        }
334
        // If the method called is a method of the current task,
335
        // then call through to the current task's setter method.
336
        $result = call_user_func_array([$this->currentTask, $fn], $args);
337
338
        // If something other than a setter method is called, then return its result.
339
        $currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
340
        if (isset($result) && ($result !== $currentTask)) {
341
            return $result;
342
        }
343
344
        return $this;
345
    }
346
347
    /**
348
     * Construct the desired task and add it to this builder.
349
     *
350
     * @param string|object $name
351
     * @param array $args
352
     *
353
     * @return \Robo\Collection\CollectionBuilder
354
     */
355
    public function build($name, $args)
356
    {
357
        $reflection = new ReflectionClass($name);
358
        $task = $reflection->newInstanceArgs($args);
359
        if (!$task) {
360
            throw new RuntimeException("Can not construct task $name");
361
        }
362
        $task = $this->fixTask($task, $args);
363
        return $this->addTaskToCollection($task);
364
    }
365
366
    /**
367
     * @param InflectionInterface $task
368
     * @param array $args
369
     *
370
     * @return \Robo\Collection\CompletionWrapper|\Robo\Task\Simulator
371
     */
372
    protected function fixTask($task, $args)
373
    {
374
        if ($task instanceof InflectionInterface) {
375
            $task->inflect($this);
376
        }
377
        if ($task instanceof BuilderAwareInterface) {
378
            $task->setBuilder($this);
379
        }
380
        // TODO: It would be better if we had a TaskIOInterface
381
        // to go with the TaskIO trait. Perhaps in 2.0.
382
        if (method_exists($task, 'setVerbosityThreshold')) {
383
            $task->setVerbosityThreshold($this->verbosityThreshold());
384
        }
385
386
        // Do not wrap our wrappers.
387
        if ($task instanceof CompletionWrapper || $task instanceof Simulator) {
388
            return $task;
389
        }
390
391
        // Remember whether or not this is a task before
392
        // it gets wrapped in any decorator.
393
        $isTask = $task instanceof TaskInterface;
394
        $isCollection = $task instanceof NestedCollectionInterface;
395
396
        // If the task implements CompletionInterface, ensure
397
        // that its 'complete' method is called when the application
398
        // terminates -- but only if its 'run' method is called
399
        // first.  If the task is added to a collection, then the
400
        // task will be unwrapped via its `original` method, and
401
        // it will be re-wrapped with a new completion wrapper for
402
        // its new collection.
403
        if ($task instanceof CompletionInterface) {
404
            $task = new CompletionWrapper(Temporary::getCollection(), $task);
405
        }
406
407
        // If we are in simulated mode, then wrap any task in
408
        // a TaskSimulator.
409
        if ($isTask && !$isCollection && ($this->isSimulated())) {
410
            $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...
411
            $task->inflect($this);
412
        }
413
414
        return $task;
415
    }
416
417
    /**
418
     * When we run the collection builder, run everything in the collection.
419
     *
420
     * @return \Robo\Result
421
     */
422
    public function run()
423
    {
424
        $this->startTimer();
425
        $result = $this->runTasks();
426
        $this->stopTimer();
427
        $result['time'] = $this->getExecutionTime();
428
        return $result;
429
    }
430
431
    /**
432
     * If there is a single task, run it; if there is a collection, run
433
     * all of its tasks.
434
     *
435
     * @return \Robo\Result
436
     */
437
    protected function runTasks()
438
    {
439
        if (!$this->collection && $this->currentTask) {
440
            return $this->currentTask->run();
441
        }
442
        return $this->getCollection()->run();
443
    }
444
445
    /**
446
     * @return string
447
     */
448
    public function getCommand()
449
    {
450
        if (!$this->collection && $this->currentTask) {
451
            $task = $this->currentTask;
452
            $task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
453
            if ($task instanceof CommandInterface) {
454
                return $task->getCommand();
455
            }
456
        }
457
458
        return $this->getCollection()->getCommand();
459
    }
460
461
    /**
462
     * @return \Robo\Collection\Collection
463
     */
464
    public function original()
465
    {
466
        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...
467
    }
468
469
    /**
470
     * Return the collection of tasks associated with this builder.
471
     *
472
     * @return CollectionInterface
473
     */
474
    public function getCollection()
475
    {
476
        if (!isset($this->collection)) {
477
            $this->collection = new Collection();
478
            $this->collection->inflect($this);
479
            $this->collection->setProgressBarAutoDisplayInterval($this->getConfig()->get(Config::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL));
480
481
            if (isset($this->currentTask)) {
482
                $this->collection->add($this->currentTask);
483
            }
484
        }
485
        return $this->collection;
486
    }
487
}
488