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
|
|||||
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
![]() |
|||||
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 The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||
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
|
|||||
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
![]() |
|||||
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
|
|||||
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
|
|||||
337 | } |
||||
338 | } |
||||
339 |
This check looks for function or method calls that always return null and whose return value is used.
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.