TasksBuilder::buildTaskFromCallable()   A
last analyzed

Complexity

Conditions 2
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 1
nop 1
crap 2
1
<?php
2
3
/*
4
 * This file is part of Rocketeer
5
 *
6
 * (c) Maxime Fabre <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 */
12
13
namespace Rocketeer\Services\Builders\Modules;
14
15
use Closure;
16
use Illuminate\Support\Str;
17
use League\Container\ContainerAwareInterface;
18
use Rocketeer\Services\Builders\TaskCompositionException;
19
use Rocketeer\Tasks\AbstractTask;
20
use Rocketeer\Tasks\Closure as ClosureTask;
21
use SuperClosure\SerializableClosure;
22
23
/**
24
 * Handles creating tasks from strings, closures, AbstractTask children, etc.
25
 */
26
class TasksBuilder extends AbstractBuilderModule
27
{
28
    /**
29
     * Build an array of tasks.
30
     *
31
     * @param array $tasks
32
     *
33
     * @return array
34
     */
35 100
    public function buildTasks(array $tasks)
36
    {
37 100
        return array_map([$this, 'buildTask'], $tasks);
38
    }
39
40
    /**
41
     * Build a task from anything.
42
     *
43
     * @param string|Closure|\Rocketeer\Tasks\AbstractTask $task
44
     * @param string|null                                  $name
45
     * @param string|null                                  $description
46
     *
47
     * @throws \Rocketeer\Services\Builders\TaskCompositionException
48
     *
49
     * @return AbstractTask
50
     */
51 442
    public function buildTask($task, $name = null, $description = null)
52
    {
53
        // Compose the task from their various types
54 442
        $task = $this->composeTask($task);
55
56
        // If the built class is invalid, cancel
57 442
        if (!$task instanceof AbstractTask) {
58 2
            throw new TaskCompositionException($task);
59
        }
60
61
        // Set task properties
62 442
        $task->setName($name);
63 442
        $task->setDescription($description);
64 442
        $task = $this->modulable->registerBashModulesOn($task);
65
66
        // Bind instance for later user
67 442
        if (!$task instanceof ClosureTask) {
68 442
            $this->container->add('rocketeer.'.$task->getIdentifier(), $task);
69 442
        }
70
71 442
        return $task;
72
    }
73
74
    //////////////////////////////////////////////////////////////////////
75
    ////////////////////////////// COMPOSING /////////////////////////////
76
    //////////////////////////////////////////////////////////////////////
77
78
    /**
79
     * Compose a Task from its various types.
80
     *
81
     * @param string|Closure|AbstractTask $task
82
     *
83
     * @throws \Rocketeer\Services\Builders\TaskCompositionException
84
     *
85
     * @return mixed|\Rocketeer\Tasks\AbstractTask
86
     */
87 442
    protected function composeTask($task)
88
    {
89
        // If already built, return it
90 442
        if ($task instanceof AbstractTask) {
91 442
            return $task;
92
        }
93
94
        // If we passed a callable, build a Closure Task
95 442
        if ($this->isCallable($task)) {
96 92
            return $this->buildTaskFromCallable($task);
97 1
        }
98
99
        // If we provided a Closure, build a Closure Task
100 442
        if ($task instanceof Closure || $task instanceof SerializableClosure) {
101 31
            return $this->buildTaskFromClosure($task);
102
        }
103
104
        // If we passed a task handle, return it
105 442
        if ($handle = $this->getTaskHandle($task)) {
106 442
            return $this->container->get($handle);
107
        }
108
109
        // If we passed a command, build a Closure Task
110 120
        if (is_array($task) || $this->isStringCommand($task) || $task === null) {
111 101
            return $this->buildTaskFromString($task);
112
        }
113
114
        // Else it's a class name, get the appropriated task
115 109
        if (!$task instanceof AbstractTask) {
116 109
            return $this->buildTaskFromClass($task);
117
        }
118
    }
119
120
    /**
121
     * Build a task from a string.
122
     *
123
     * @param string|string[] $task
124
     *
125
     * @return AbstractTask
126
     */
127 101
    public function buildTaskFromString($task)
128
    {
129 101
        $closure = $this->wrapStringTasks($task);
130
131 101
        return $this->buildTaskFromClosure($closure, $task);
0 ignored issues
show
Bug introduced by Maxime Fabre
It seems like $task defined by parameter $task on line 127 can also be of type array<integer,string>; however, Rocketeer\Services\Build...:buildTaskFromClosure() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
132
    }
133
134
    /**
135
     * Build a task from a Closure or a string command.
136
     *
137
     * @param SerializableClosure|Closure $callback
138
     * @param string|null                 $stringTask
139
     *
140
     * @return \Rocketeer\Tasks\AbstractTask
141
     */
142 116
    public function buildTaskFromClosure($callback, $stringTask = null)
143
    {
144
        /** @var ClosureTask $task */
145 116
        $task = $this->buildTaskFromClass(ClosureTask::class);
146 116
        $task->setClosure($callback);
147
148
        // If we had an original string used, store it on
149
        // the task for easier reflection
150 116
        if ($stringTask) {
151 101
            $task->setStringTask($stringTask);
152 101
        }
153
154 116
        return $task;
155
    }
156
157
    /**
158
     * Build a task from its name.
159
     *
160
     * @param string|\Rocketeer\Tasks\AbstractTask $task
161
     *
162
     * @throws TaskCompositionException
163
     *
164
     * @return AbstractTask
165
     */
166 133
    public function buildTaskFromClass($task)
167
    {
168 133
        if (is_object($task) && $task instanceof AbstractTask) {
169 5
            return $task;
170
        }
171
172
        // Cancel if class doesn't exist
173 133
        if (!$class = $this->taskClassExists($task)) {
174 1
            throw new TaskCompositionException($task);
175
        }
176
177
        // Build class
178 132
        $class = new $class();
179 132
        if ($class instanceof ContainerAwareInterface) {
180 132
            $class->setContainer($this->container);
181 132
        }
182
183 132
        return $class;
184
    }
185
186
    /**
187
     * Build a task from a callable.
188
     *
189
     * @param callable $callable
190
     *
191
     * @return ClosureTask
192
     */
193 92
    protected function buildTaskFromCallable($callable)
194
    {
195 92
        $task = new ClosureTask();
196 92
        $task->setContainer($this->container);
197
        $task->setClosure(function () use ($callable, $task) {
198 3
            list($class, $method) = is_array($callable) ? $callable : explode('::', $callable);
199
200 3
            return $this->getContainer()->get($class)->$method($task);
201 92
        });
202
203 92
        return $task;
204
    }
205
206
    //////////////////////////////////////////////////////////////////////
207
    ////////////////////////////// LOOKUPS ///////////////////////////////
208
    //////////////////////////////////////////////////////////////////////
209
210
    /**
211
     * Check if a class with the given task name exists.
212
     *
213
     * @param string $task
214
     *
215
     * @return string|false
216
     */
217 133
    protected function taskClassExists($task)
218
    {
219 133
        return $this->modulable->findQualifiedName($task, 'tasks');
220
    }
221
222
    ////////////////////////////////////////////////////////////////////
223
    /////////////////////////////// HELPERS ////////////////////////////
224
    ////////////////////////////////////////////////////////////////////
225
226
    /**
227
     * Get the handle of a task from its name.
228
     *
229
     * @param string|AbstractTask $task
230
     *
231
     * @return string|null
232
     */
233 442
    protected function getTaskHandle($task)
234
    {
235
        // Check the handle if possible
236 442
        if (!is_string($task)) {
237 4
            return;
238
        }
239
240
        // Compute the handle and check it's bound
241 442
        $handle = 'rocketeer.tasks.'.Str::snake(class_basename($task), '-');
242 442
        $task = $this->container->has($handle) ? $handle : null;
243
244 442
        return $task;
245
    }
246
247
    /**
248
     * Check if a string is a command or a task.
249
     *
250
     * @param string|Closure|\Rocketeer\Tasks\AbstractTask $string
251
     *
252
     * @return bool
253
     */
254 119
    protected function isStringCommand($string)
255
    {
256 119
        return is_string($string) && !$this->taskClassExists($string) && !$this->container->has('rocketeer.tasks.'.$string);
257
    }
258
259
    /**
260
     * Check if a task is a callable.
261
     *
262
     * @param array|string|Closure $task
263
     *
264
     * @return bool
265
     */
266 442
    public function isCallable($task)
267
    {
268
        // Check for container bindings
269 442
        if (is_array($task)) {
270 84
            return count($task) === 2 && ($this->container->has($task[0]) || is_callable($task));
271
        }
272
273 442
        return is_callable($task) && !$task instanceof Closure && !$task instanceof SerializableClosure;
274
    }
275
276
    /**
277
     * @param string|array $stringTask
278
     *
279
     * @return Closure
280
     */
281
    public function wrapStringTasks($stringTask)
282
    {
283 101
        return function (AbstractTask $task) use ($stringTask) {
284 8
            return $task->runForCurrentRelease($stringTask);
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method runForCurrentRelease does not exist on object<Rocketeer\Tasks\AbstractTask>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
285 101
        };
286
    }
287
288
    /**
289
     * @return string[]
290
     */
291 442
    public function getProvided()
292
    {
293
        return [
294 442
            'buildTask',
295 442
            'buildTaskFromClass',
296 442
            'buildTaskFromClosure',
297 442
            'buildTaskFromString',
298 442
            'buildTasks',
299 442
            'isCallable',
300 442
            'wrapStringTasks',
301 442
        ];
302
    }
303
}
304