Issues (91)

src/DependencyInjection/Configuration.php (7 issues)

1
<?php
2
3
namespace Zenstruck\ScheduleBundle\DependencyInjection;
4
5
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
6
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
7
use Symfony\Component\Config\Definition\ConfigurationInterface;
8
use Zenstruck\ScheduleBundle\Schedule\CronExpression;
9
use Zenstruck\ScheduleBundle\Schedule\Extension\SingleServerExtension;
10
use Zenstruck\ScheduleBundle\Schedule\Extension\WithoutOverlappingExtension;
11
12
/**
13
 * @author Kevin Bond <[email protected]>
14
 */
15
final class Configuration implements ConfigurationInterface
16
{
17 42
    public function getConfigTreeBuilder(): TreeBuilder
18
    {
19 42
        $treeBuilder = new TreeBuilder('zenstruck_schedule');
20
21 42
        $treeBuilder->getRootNode()
22 42
            ->children()
23 42
                ->scalarNode('without_overlapping_lock_factory')
24 42
                    ->info('The LockFactory service to use for the without overlapping extension')
25 42
                    ->example('lock.default.factory')
26 42
                    ->defaultNull()
27 42
                ->end()
28 42
                ->scalarNode('single_server_lock_factory')
29 42
                    ->info('The LockFactory service to use for the single server extension - be sure to use a "remote store" (https://symfony.com/doc/current/components/lock.html#remote-stores)')
30 42
                    ->example('lock.redis.factory')
31 42
                    ->defaultNull()
32 42
                ->end()
33 42
                ->scalarNode('http_client')
34 42
                    ->info('The HttpClient service to use')
35 42
                    ->example('http_client')
36 42
                    ->defaultNull()
37 42
                ->end()
38 42
                ->scalarNode('timezone')
39 42
                    ->info('The default timezone for tasks (override at task level), null for system default')
40 42
                    ->example('America/New_York')
41 42
                    ->defaultNull()
42 42
                    ->validate()
43 42
                        ->ifNotInArray(\timezone_identifiers_list())
0 ignored issues
show
Are you sure the usage of timezone_identifiers_list() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
44 42
                        ->thenInvalid('Timezone %s is not available')
45 42
                    ->end()
46 42
                ->end()
47 42
                ->arrayNode('messenger')
48 42
                    ->canBeEnabled()
49 42
                    ->children()
50 42
                        ->scalarNode('message_bus')
51 42
                            ->defaultValue('message_bus')
52 42
                            ->cannotBeEmpty()
53 42
                            ->info('The message bus to use')
54 42
                        ->end()
55 42
                    ->end()
56 42
                ->end()
57 42
                ->arrayNode('mailer')
58 42
                    ->canBeEnabled()
59 42
                    ->children()
60 42
                        ->scalarNode('service')
61 42
                            ->defaultValue('mailer')
62 42
                            ->cannotBeEmpty()
63 42
                            ->info('The mailer service to use')
64 42
                        ->end()
65 42
                        ->scalarNode('default_from')
66 42
                            ->info('The default "from" email address (use if no mailer default from is configured)')
67 42
                            ->defaultNull()
68 42
                        ->end()
69 42
                        ->scalarNode('default_to')
70 42
                            ->info('The default "to" email address (can be overridden by extension)')
71 42
                            ->defaultNull()
72 42
                        ->end()
73 42
                        ->scalarNode('subject_prefix')
74 42
                            ->info('The prefix to use for email subjects (use to distinguish between different application schedules)')
75 42
                            ->example('"[Acme Inc Website]"')
76 42
                            ->defaultNull()
77 42
                        ->end()
78 42
                    ->end()
79 42
                ->end()
80 42
                ->arrayNode('schedule_extensions')
81 42
                    ->addDefaultsIfNotSet()
82 42
                    ->children()
83 42
                        ->arrayNode('environments')
84 42
                            ->beforeNormalization()->castToArray()->end()
85 42
                            ->scalarPrototype()->end()
86 42
                            ->info('Set the environment(s) you only want the schedule to run in.')
87 42
                            ->example('[prod, staging]')
88 42
                        ->end()
89 42
                        ->arrayNode('on_single_server')
90 42
                            ->info('Run schedule on only one server')
91 42
                            ->canBeEnabled()
92 42
                            ->children()
93 42
                                ->integerNode('ttl')
94 42
                                    ->info('Maximum expected lock duration in seconds')
95 42
                                    ->defaultValue(SingleServerExtension::DEFAULT_TTL)
96 42
                                ->end()
97 42
                            ->end()
98 42
                        ->end()
99 42
                        ->append(self::createEmailExtension('email_on_failure', 'Send email if schedule fails'))
100 42
                        ->append(self::createPingExtension('ping_before', 'Ping a url before schedule runs'))
101 42
                        ->append(self::createPingExtension('ping_after', 'Ping a url after schedule runs'))
102 42
                        ->append(self::createPingExtension('ping_on_success', 'Ping a url if the schedule successfully ran'))
103 42
                        ->append(self::createPingExtension('ping_on_failure', 'Ping a url if the schedule failed'))
104 42
                    ->end()
105 42
                ->end()
106 42
                ->append(self::taskConfiguration())
107 42
            ->end()
108
        ;
109
110 42
        return $treeBuilder;
111
    }
112
113 42
    private static function taskConfiguration(): ArrayNodeDefinition
114
    {
115 42
        $treeBuilder = new TreeBuilder('tasks');
116 42
        $node = $treeBuilder->getRootNode();
117
118
        $node
119 42
            ->example([
120
                [
121 42
                    'task' => 'send:sales-report --detailed',
122
                    'frequency' => '0 * * * *',
123
                    'description' => 'Send sales report hourly',
124
                    'without_overlapping' => '~',
125
                    'only_between' => '9-17',
126
                    'ping_on_success' => 'https://example.com/hourly-report-health-check',
127
                    'email_on_failure' => '[email protected]',
128
                ],
129
            ])
130 42
            ->arrayPrototype()
0 ignored issues
show
The method arrayPrototype() does not exist on Symfony\Component\Config...\Builder\NodeDefinition. It seems like you code against a sub-type of Symfony\Component\Config...\Builder\NodeDefinition such as Symfony\Component\Config...der\ArrayNodeDefinition. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

130
            ->/** @scrutinizer ignore-call */ arrayPrototype()
Loading history...
131 42
                ->validate()
132
                    ->ifTrue(function($v) {
133 16
                        return [null] === $v['task'] && !$v['description'];
134 42
                    })
135 42
                    ->thenInvalid('"null" tasks must have a description.')
136 42
                ->end()
137 42
                ->children()
138 42
                    ->arrayNode('task')
139 42
                        ->info('Defaults to CommandTask, prefix with "bash:" to create ProcessTask, prefix url with "ping:" to create PingTask, pass array of commands to create CompoundTask (optionally keyed by description)')
140 42
                        ->example('"my:command arg1 --option1=value", "bash:/bin/my-script" or "ping:https://example.com"')
141 42
                        ->validate()
142
                            ->ifTrue(function($v) {
143 22
                                foreach ($v as $item) {
144 22
                                    if ('' === (string) $item) {
145 3
                                        return true;
146
                                    }
147
                                }
148
149 19
                                return false;
150 42
                            })
151 42
                            ->thenInvalid('Task cannot be empty value.')
152 42
                        ->end()
153 42
                        ->beforeNormalization()
154 42
                            ->castToArray()
155 42
                        ->end()
156 42
                        ->isRequired()
157 42
                        ->cannotBeEmpty()
158 42
                        ->scalarPrototype()->end()
159 42
                    ->end()
160 42
                    ->scalarNode('frequency')
161 42
                        ->info('Cron expression')
162 42
                        ->example('0 * * * *')
163 42
                        ->isRequired()
164 42
                        ->cannotBeEmpty()
165 42
                        ->validate()
166
                            ->ifTrue(function($v) {
167
                                try {
168 17
                                    new CronExpression($v, 'context');
169 1
                                } catch (\InvalidArgumentException $e) {
170 1
                                    return true;
171
                                }
172
173 16
                                return false;
174 42
                            })
175 42
                            ->thenInvalid('%s is an invalid cron expression.')
176 42
                        ->end()
177 42
                    ->end()
178 42
                    ->scalarNode('description')
179 42
                        ->info('Task description')
180 42
                        ->defaultNull()
181 42
                    ->end()
182 42
                    ->scalarNode('timezone')
183 42
                        ->info('The timezone for this task, null for system default')
184 42
                        ->example('America/New_York')
185 42
                        ->defaultNull()
186 42
                        ->validate()
187 42
                            ->ifNotInArray(\timezone_identifiers_list())
0 ignored issues
show
Are you sure the usage of timezone_identifiers_list() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
188 42
                            ->thenInvalid('Timezone %s is not available')
189 42
                        ->end()
190 42
                    ->end()
191 42
                    ->arrayNode('without_overlapping')
192 42
                        ->info('Prevent task from running if still running from previous run')
193 42
                        ->canBeEnabled()
194 42
                        ->children()
195 42
                            ->integerNode('ttl')
196 42
                                ->info('Maximum expected lock duration in seconds')
197 42
                                ->defaultValue(WithoutOverlappingExtension::DEFAULT_TTL)
198 42
                            ->end()
199 42
                        ->end()
200 42
                    ->end()
201 42
                    ->arrayNode('only_between')
202 42
                        ->info('Only run between given times (alternatively enable by passing a range, ie "9:00-17:00"')
203 42
                        ->canBeEnabled()
204 42
                        ->beforeNormalization()
205 42
                            ->ifString()
206
                            ->then(function($v) {
207 1
                                [$start, $end] = \explode('-', $v);
208
209
                                return [
210 1
                                    'enabled' => true,
211 1
                                    'start' => $start,
212 1
                                    'end' => $end,
213
                                ];
214 42
                            })
215 42
                        ->end()
216 42
                        ->children()
217 42
                            ->scalarNode('start')
218 42
                                ->example('9:00')
219 42
                                ->isRequired()
220 42
                            ->end()
221 42
                            ->scalarNode('end')
222 42
                                ->example('17:00')
223 42
                                ->isRequired()
224 42
                            ->end()
225 42
                        ->end()
226 42
                    ->end()
227 42
                    ->arrayNode('unless_between')
228 42
                        ->info('Skip when between given times (alternatively enable by passing a range, ie "17:00-06:00"')
229 42
                        ->canBeEnabled()
230 42
                        ->beforeNormalization()
231 42
                            ->ifString()
232
                            ->then(function($v) {
233 1
                                [$start, $end] = \explode('-', $v);
234
235
                                return [
236 1
                                    'enabled' => true,
237 1
                                    'start' => $start,
238 1
                                    'end' => $end,
239
                                ];
240 42
                            })
241 42
                        ->end()
242 42
                        ->children()
243 42
                            ->scalarNode('start')
244 42
                                ->example('17:00')
245 42
                                ->isRequired()
246 42
                            ->end()
247 42
                                ->scalarNode('end')
248 42
                                ->example('06:00')
249 42
                                ->isRequired()
250 42
                            ->end()
251 42
                        ->end()
252 42
                    ->end()
253 42
                    ->append(self::createPingExtension('ping_before', 'Ping a url before task runs'))
254 42
                    ->append(self::createPingExtension('ping_after', 'Ping a url after task runs'))
255 42
                    ->append(self::createPingExtension('ping_on_success', 'Ping a url if the task successfully ran'))
256 42
                    ->append(self::createPingExtension('ping_on_failure', 'Ping a url if the task failed'))
257 42
                    ->append(self::createEmailExtension('email_after', 'Send email after task runs'))
258 42
                    ->append(self::createEmailExtension('email_on_failure', 'Send email if task fails'))
259 42
                ->end()
260 42
            ->end()
261
        ;
262
263 42
        return $node;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $node returns the type Symfony\Component\Config...\Builder\NodeDefinition which includes types incompatible with the type-hinted return Symfony\Component\Config...der\ArrayNodeDefinition.
Loading history...
264
    }
265
266 42
    private static function createEmailExtension(string $name, string $description): ArrayNodeDefinition
267
    {
268 42
        $treeBuilder = new TreeBuilder($name);
269 42
        $node = $treeBuilder->getRootNode();
270
271
        $node
272 42
            ->info($description.' (alternatively enable by passing a "to" email)')
273 42
            ->canBeEnabled()
0 ignored issues
show
The method canBeEnabled() does not exist on Symfony\Component\Config...\Builder\NodeDefinition. It seems like you code against a sub-type of Symfony\Component\Config...\Builder\NodeDefinition such as Symfony\Component\Config...der\ArrayNodeDefinition. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

273
            ->/** @scrutinizer ignore-call */ canBeEnabled()
Loading history...
274 42
            ->beforeNormalization()
275 42
                ->ifString()
276
                ->then(function($v) {
277
                    return [
278 1
                        'enabled' => true,
279 1
                        'to' => $v,
280
                        'subject' => null,
281
                    ];
282 42
                })
283 42
            ->end()
284 42
            ->children()
285 42
                ->scalarNode('to')
286 42
                    ->info('Email address to send email to (leave blank to use "zenstruck_schedule.mailer.default_to")')
287 42
                    ->defaultNull()
288 42
                ->end()
289 42
                ->scalarNode('subject')
290 42
                    ->info('Email subject (leave blank to use extension default)')
291 42
                    ->defaultNull()
292 42
                ->end()
293 42
            ->end()
294
        ;
295
296 42
        return $node;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $node returns the type Symfony\Component\Config...\Builder\NodeDefinition which includes types incompatible with the type-hinted return Symfony\Component\Config...der\ArrayNodeDefinition.
Loading history...
297
    }
298
299 42
    private static function createPingExtension(string $name, string $description): ArrayNodeDefinition
300
    {
301 42
        $treeBuilder = new TreeBuilder($name);
302 42
        $node = $treeBuilder->getRootNode();
303
304
        $node
305 42
            ->info($description.' (alternatively enable by passing a url)')
306 42
            ->canBeEnabled()
307 42
            ->beforeNormalization()
308 42
                ->ifString()
309
                ->then(function($v) {
310
                    return [
311 1
                        'enabled' => true,
312 1
                        'url' => $v,
313 1
                        'method' => 'GET',
314
                        'options' => [],
315
                    ];
316 42
                })
317 42
            ->end()
318 42
            ->children()
319 42
                ->scalarNode('url')
320 42
                    ->info('The url to ping')
321 42
                    ->isRequired()
322 42
                    ->cannotBeEmpty()
323 42
                ->end()
324 42
                ->scalarNode('method')
325 42
                    ->info('The HTTP method to use')
326 42
                    ->defaultValue('GET')
327 42
                    ->cannotBeEmpty()
328 42
                ->end()
329 42
                ->arrayNode('options')
330 42
                    ->info('See HttpClientInterface::OPTIONS_DEFAULTS')
331 42
                    ->scalarPrototype()->end()
332 42
                ->end()
333 42
            ->end()
334
        ;
335
336 42
        return $node;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $node returns the type Symfony\Component\Config...\Builder\NodeDefinition which includes types incompatible with the type-hinted return Symfony\Component\Config...der\ArrayNodeDefinition.
Loading history...
337
    }
338
}
339