Completed
Push — master ( 8c5bee...33e30f )
by
unknown
06:57 queued 04:51
created

SculpinBundle/Command/ContainerDebugCommand.php (6 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is a part of Sculpin.
7
 *
8
 * (c) Dragonfly Development Inc.
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 *
13
 * This command is derived from the Symfony container debug command,
14
 * (c) Fabien Potencier <[email protected]>
15
 */
16
17
namespace Sculpin\Bundle\SculpinBundle\Command;
18
19
use Sculpin\Core\Console\Command\ContainerAwareCommand;
20
use Symfony\Component\Console\Input\InputArgument;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\DependencyInjection\Alias;
25
use Symfony\Component\DependencyInjection\Container;
26
use Symfony\Component\DependencyInjection\ContainerBuilder;
27
use Symfony\Component\DependencyInjection\Definition;
28
29
/**
30
 * A console command for retrieving information about services
31
 *
32
 * @author Ryan Weaver <[email protected]>
33
 */
34
final class ContainerDebugCommand extends ContainerAwareCommand
35
{
36
    /**
37
     * {@inheritdoc}
38
     */
39
    protected function configure(): void
40
    {
41
        $this
42
            ->setName('container:debug')
43
            ->setDefinition([
44
                new InputArgument('name', InputArgument::OPTIONAL, 'A service name (foo)'),
45
                new InputOption(
46
                    'show-private',
47
                    null,
48
                    InputOption::VALUE_NONE,
49
                    'Use to show public *and* private services'
50
                ),
51
                new InputOption(
52
                    'tag',
53
                    null,
54
                    InputOption::VALUE_REQUIRED,
55
                    'Show all services with a specific tag'
56
                ),
57
                new InputOption(
58
                    'tags',
59
                    null,
60
                    InputOption::VALUE_NONE,
61
                    'Displays tagged services for an application'
62
                ),
63
                new InputOption(
64
                    'parameter',
65
                    null,
66
                    InputOption::VALUE_REQUIRED,
67
                    'Displays a specific parameter for an application'
68
                ),
69
                new InputOption(
70
                    'parameters',
71
                    null,
72
                    InputOption::VALUE_NONE,
73
                    'Displays parameters for an application'
74
                )
75
            ])
76
            ->setDescription('Displays current services for an application')
77
            ->setHelp(<<<EOF
78
The <info>%command.name%</info> command displays all configured <comment>public</comment> services:
79
80
  <info>php %command.full_name%</info>
81
82
To get specific information about a service, specify its name:
83
84
  <info>php %command.full_name% validator</info>
85
86
By default, private services are hidden. You can display all services by
87
using the --show-private flag:
88
89
  <info>php %command.full_name% --show-private</info>
90
91
Use the --tags option to display tagged <comment>public</comment> services grouped by tag:
92
93
  <info>php %command.full_name% --tags</info>
94
95
Find all services with a specific tag by specifying the tag name with the --tag option:
96
97
  <info>php %command.full_name% --tag=form.type</info>
98
99
Use the --parameters option to display all parameters:
100
101
  <info>php %command.full_name% --parameters</info>
102
103
Display a specific parameter by specifying his name with the --parameter option:
104
105
  <info>php %command.full_name% --parameter=kernel.debug</info>
106
EOF
107
            )
108
        ;
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     *
114
     * @throws \LogicException
115
     */
116
    protected function execute(InputInterface $input, OutputInterface $output): void
117
    {
118
        $this->validateInput($input);
119
120
        if ($input->getOption('parameters')) {
121
            if (!$this->getContainer() instanceof Container) {
122
                return;
123
            }
124
            $parameters = $this->getContainer()->getParameterBag()->all();
125
126
            // Sort parameters alphabetically
127
            ksort($parameters);
128
129
            $this->outputParameters($output, $parameters);
130
131
            return;
132
        }
133
134
        $parameter = $input->getOption('parameter');
135
        if (null !== $parameter) {
136
            $output->write($this->formatParameter($this->getContainer()->getParameter($parameter)));
137
138
            return;
139
        }
140
141
        if ($input->getOption('tags')) {
142
            $this->outputTags($output, $input->getOption('show-private'));
143
144
            return;
145
        }
146
147
        $tag = $input->getOption('tag');
148
        $serviceIds = [];
149
        if ($this->getContainer() instanceof ContainerBuilder) {
150
            if (null !== $tag) {
151
                $serviceIds = array_keys($this->getContainer()->findTaggedServiceIds($tag));
152
            } else {
153
                $serviceIds = $this->getContainer()->getServiceIds();
154
            }
155
        }
156
157
        // sort so that it reads like an index of services
158
        asort($serviceIds);
159
160
        $name = $input->getArgument('name');
161
        if ($name) {
162
            $this->outputService($output, $name);
0 ignored issues
show
It seems like $name defined by $input->getArgument('name') on line 160 can also be of type array<integer,string>; however, Sculpin\Bundle\SculpinBu...ommand::outputService() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
163
        } else {
164
            $this->outputServices($output, $serviceIds, $input->getOption('show-private'), $tag);
165
        }
166
    }
167
168
    private function validateInput(InputInterface $input)
169
    {
170
        $options = ['tags', 'tag', 'parameters', 'parameter'];
171
172
        $optionsCount = 0;
173
        foreach ($options as $option) {
174
            if ($input->getOption($option)) {
175
                $optionsCount++;
176
            }
177
        }
178
179
        $name = $input->getArgument('name');
180
        if ((null !== $name) && ($optionsCount > 0)) {
181
            throw new \InvalidArgumentException(
182
                'The options tags, tag, parameters & parameter can not be combined with the service name argument.'
183
            );
184
        } elseif ((null === $name) && $optionsCount > 1) {
185
            throw new \InvalidArgumentException(
186
                'The options tags, tag, parameters & parameter can not be combined together.'
187
            );
188
        }
189
    }
190
191
    private function outputServices(
192
        OutputInterface $output,
193
        $serviceIds,
194
        $showPrivate = false,
195
        $showTagAttributes = null
196
    ): void {
197
        // set the label to specify public or public+private
198
        if ($showPrivate) {
199
            $label = '<comment>Public</comment> and <comment>private</comment> services';
200
        } else {
201
            $label = '<comment>Public</comment> services';
202
        }
203
        if ($showTagAttributes) {
204
            $label .= ' with tag <info>'.$showTagAttributes.'</info>';
205
        }
206
207
        $output->writeln($this->getHelper('formatter')->formatSection('container', $label));
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method formatSection() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\FormatterHelper.

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...
208
209
        // loop through to get space needed and filter private services
210
        $maxName = 4;
211
        $maxTags = [];
212
        foreach ($serviceIds as $key => $serviceId) {
213
            $definition = $this->resolveServiceDefinition($serviceId);
214
215
            if ($definition instanceof Definition) {
216
                // filter out private services unless shown explicitly
217
                if (!$showPrivate && !$definition->isPublic()) {
218
                    unset($serviceIds[$key]);
219
                    continue;
220
                }
221
222
                if (null !== $showTagAttributes) {
223
                    $tags = $definition->getTag($showTagAttributes);
224
                    foreach ($tags as $tag) {
225
                        foreach ($tag as $key => $value) {
226
                            if (!isset($maxTags[$key])) {
227
                                $maxTags[$key] = strlen($key);
228
                            }
229
                            if (strlen($value) > $maxTags[$key]) {
230
                                $maxTags[$key] = strlen($value);
231
                            }
232
                        }
233
                    }
234
                }
235
            }
236
237
            if (strlen($serviceId) > $maxName) {
238
                $maxName = strlen($serviceId);
239
            }
240
        }
241
        $format = '%-'.$maxName.'s ';
242
        $format .= implode("", array_map(function ($length) {
243
            return "%-{$length}s ";
244
        }, $maxTags));
245
        $format .=  '%s';
246
247
        // the title field needs extra space to make up for comment tags
248
        $format1 = '%-'.($maxName + 19).'s ';
249
        $format1 .= implode("", array_map(function ($length) {
250
            return '%-'.($length + 19).'s ';
251
        }, $maxTags));
252
        $format1 .= '%s';
253
254
        $tags = [];
255
        foreach ($maxTags as $tagName => $length) {
256
            $tags[] = '<comment>'.$tagName.'</comment>';
257
        }
258
        $output->writeln(vsprintf($format1, $this->buildArgumentsArray(
259
            '<comment>Service Id</comment>',
260
            '<comment>Class Name</comment>',
261
            $tags
262
        )));
263
264
        foreach ($serviceIds as $serviceId) {
265
            $definition = $this->resolveServiceDefinition($serviceId);
266
267
            if ($definition instanceof Definition) {
268
                $lines = [];
269
                if (null !== $showTagAttributes) {
270
                    foreach ($definition->getTag($showTagAttributes) as $key => $tag) {
271
                        $tagValues = [];
272
                        foreach (array_keys($maxTags) as $tagName) {
273
                            $tagValues[] = isset($tag[$tagName]) ? $tag[$tagName] : "";
274
                        }
275
                        if (0 === $key) {
276
                            $lines[] = $this->buildArgumentsArray(
277
                                $serviceId,
278
                                $definition->getClass(),
279
                                $tagValues
280
                            );
281
                        } else {
282
                            $lines[] = $this->buildArgumentsArray('  "', '', $tagValues);
283
                        }
284
                    }
285
                } else {
286
                    $lines[] = $this->buildArgumentsArray($serviceId, $definition->getClass());
287
                }
288
289
                foreach ($lines as $arguments) {
290
                    $output->writeln(vsprintf($format, $arguments));
291
                }
292
            } elseif ($definition instanceof Alias) {
293
                $alias = $definition;
294
                $output->writeln(vsprintf($format, $this->buildArgumentsArray(
295
                    $serviceId,
296
                    sprintf('<comment>alias for</comment> <info>%s</info>', (string) $alias),
297
                    count($maxTags) ? array_fill(0, count($maxTags), "") : []
298
                )));
299
            } else {
300
                // we have no information (happens with "service_container")
301
                $service = $definition;
302
                $output->writeln(vsprintf($format, $this->buildArgumentsArray(
303
                    $serviceId,
304
                    get_class($service),
305
                    count($maxTags) ? array_fill(0, count($maxTags), "") : []
306
                )));
307
            }
308
        }
309
    }
310
311
    private function buildArgumentsArray($serviceId, $className, array $tagAttributes = []): array
312
    {
313
        $arguments = [$serviceId];
314
        foreach ($tagAttributes as $tagAttribute) {
315
            $arguments[] = $tagAttribute;
316
        }
317
        $arguments[] = $className;
318
319
        return $arguments;
320
    }
321
322
    /**
323
     * Renders detailed service information about one service
324
     */
325
    private function outputService(OutputInterface $output, string $serviceId)
326
    {
327
        $definition = $this->resolveServiceDefinition($serviceId);
328
329
        $label = sprintf('Information for service <info>%s</info>', $serviceId);
330
        $output->writeln($this->getHelper('formatter')->formatSection('container', $label));
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method formatSection() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\FormatterHelper.

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...
331
        $output->writeln('');
332
333
        if ($definition instanceof Definition) {
334
            $output->writeln(sprintf('<comment>Service Id</comment>       %s', $serviceId));
335
            $output->writeln(sprintf('<comment>Class</comment>            %s', $definition->getClass() ?: "-"));
336
337
            $tags = $definition->getTags();
338
            if (count($tags)) {
339
                $output->writeln('<comment>Tags</comment>');
340
                foreach ($tags as $tagName => $tagData) {
341
                    foreach ($tagData as $singleTagData) {
342
                        $output->writeln(sprintf(
343
                            '    - %-30s (%s)',
344
                            $tagName,
345
                            implode(', ', array_map(
346
                                function ($key, $value) {
347
                                    return sprintf('<info>%s</info>: %s', $key, $value);
348
                                },
349
                                array_keys($singleTagData),
350
                                array_values($singleTagData)
351
                            ))
352
                        ));
353
                    }
354
                }
355
            } else {
356
                $output->writeln('<comment>Tags</comment>             -');
357
            }
358
359
            $public = $definition->isPublic() ? 'yes' : 'no';
360
            $output->writeln(sprintf('<comment>Public</comment>           %s', $public));
361
362
            $synthetic = $definition->isSynthetic() ? 'yes' : 'no';
363
            $output->writeln(sprintf('<comment>Synthetic</comment>        %s', $synthetic));
364
365
            $file = $definition->getFile() ? $definition->getFile() : '-';
366
            $output->writeln(sprintf('<comment>Required File</comment>    %s', $file));
367
        } elseif ($definition instanceof Alias) {
368
            $alias = $definition;
369
            $output->writeln(sprintf('This service is an alias for the service <info>%s</info>', (string) $alias));
370
        } else {
371
            // edge case (but true for "service_container", all we have is the service itself
372
            $service = $definition;
373
            $output->writeln(sprintf('<comment>Service Id</comment>   %s', $serviceId));
374
            $output->writeln(sprintf('<comment>Class</comment>        %s', get_class($service)));
375
        }
376
    }
377
378
    private function outputParameters(OutputInterface $output, array $parameters): void
379
    {
380
        $output->writeln($this->getHelper('formatter')->formatSection('container', 'List of parameters'));
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method formatSection() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\FormatterHelper.

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...
381
382
        $maxTerminalWidth   = (int) (getenv('COLUMNS') ?? 80);
383
        $maxParameterWidth  = 0;
384
        $maxValueWidth      = 0;
385
386
        // Determine max parameter & value length
387
        foreach ($parameters as $parameter => $value) {
388
            $parameterWidth = strlen($parameter);
389
            if ($parameterWidth > $maxParameterWidth) {
390
                $maxParameterWidth = $parameterWidth;
391
            }
392
393
            $valueWith = strlen($this->formatParameter($value));
394
            if ($valueWith > $maxValueWidth) {
395
                $maxValueWidth = $valueWith;
396
            }
397
        }
398
399
        $maxValueWidth = min($maxValueWidth, $maxTerminalWidth - $maxParameterWidth - 1);
400
401
        $formatTitle = '%-'.($maxParameterWidth + 19).'s %-'.($maxValueWidth + 19).'s';
402
        $format = '%-'.$maxParameterWidth.'s %-'.$maxValueWidth.'s';
403
404
        $output->writeln(sprintf($formatTitle, '<comment>Parameter</comment>', '<comment>Value</comment>'));
405
406
        foreach ($parameters as $parameter => $value) {
407
            $splits = str_split($this->formatParameter($value), $maxValueWidth);
408
409
            foreach ($splits as $index => $split) {
410
                if (0 === $index) {
411
                    $output->writeln(sprintf($format, $parameter, $split));
412
                } else {
413
                    $output->writeln(sprintf($format, ' ', $split));
414
                }
415
            }
416
        }
417
    }
418
419
    /**
420
     * Given an array of service IDs, this returns the array of corresponding
421
     * Definition and Alias objects that those ids represent.
422
     *
423
     * @param string $serviceId The service id to resolve
424
     *
425
     * @return Definition|Alias
426
     * @throws \Exception
427
     */
428
    private function resolveServiceDefinition($serviceId)
429
    {
430
        $container = $this->getContainer();
431
        if ($container instanceof ContainerBuilder) {
432
            if ($container->hasDefinition($serviceId)) {
433
                return $container->getDefinition($serviceId);
434
            }
435
436
            // Some service IDs don't have a Definition, they're simply an Alias
437
            if ($container->hasAlias($serviceId)) {
438
                return $container->getAlias($serviceId);
439
            }
440
        }
441
442
        // the service has been injected in some special way, just return the service
443
        return $container->get($serviceId);
444
    }
445
446
    /**
447
     * Renders list of tagged services grouped by tag
448
     *
449
     * @param OutputInterface $output
450
     * @param bool $showPrivate
451
     *
452
     * @throws \Exception
453
     */
454
    private function outputTags(OutputInterface $output, bool $showPrivate = false): void
455
    {
456
        $container = $this->getContainer();
457
        if (! $container instanceof ContainerBuilder) {
458
            return;
459
        }
460
        $tags = $container->findTags();
461
        asort($tags);
462
463
        $label = 'Tagged services';
464
        $output->writeln($this->getHelper('formatter')->formatSection('container', $label));
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method formatSection() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\FormatterHelper.

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...
465
466
        foreach ($tags as $tag) {
467
            $serviceIds = $container->findTaggedServiceIds($tag);
468
469
            foreach ($serviceIds as $serviceId => $attributes) {
470
                $definition = $this->resolveServiceDefinition($serviceId);
471
                if ($definition instanceof Definition) {
472
                    if (!$showPrivate && !$definition->isPublic()) {
473
                        unset($serviceIds[$serviceId]);
474
                        continue;
475
                    }
476
                }
477
            }
478
479
            if (count($serviceIds) === 0) {
480
                continue;
481
            }
482
483
            $output->writeln($this->getHelper('formatter')->formatSection('tag', $tag));
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method formatSection() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\FormatterHelper.

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...
484
485
            foreach ($serviceIds as $serviceId => $attributes) {
486
                $output->writeln($serviceId);
487
            }
488
489
            $output->writeln('');
490
        }
491
    }
492
493
    private function formatParameter($value)
494
    {
495
        if (is_bool($value) || is_array($value) || (null === $value)) {
496
            return json_encode($value);
497
        }
498
499
        return $value;
500
    }
501
}
502