Completed
Pull Request — master (#26)
by Emanuele
03:28
created

LoadFeaturesCommand   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 111
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 8

Test Coverage

Coverage 44.59%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 14
c 1
b 0
f 0
lcom 0
cbo 8
dl 0
loc 111
rs 10
ccs 33
cts 74
cp 0.4459

3 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 17 1
C execute() 0 43 7
B findFeatureNodes() 0 33 6
1
<?php
2
3
namespace Ae\FeatureBundle\Command;
4
5
use Ae\FeatureBundle\Twig\Node\FeatureNode;
6
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
7
use Symfony\Component\Console\Input\InputArgument;
8
use Symfony\Component\Console\Input\InputInterface;
9
use Symfony\Component\Console\Input\InputOption;
10
use Symfony\Component\Console\Output\OutputInterface;
11
use Symfony\Component\Finder\Finder;
12
use Twig_Node;
13
14
/**
15
 * Load features from some directories.
16
 */
17
class LoadFeaturesCommand extends ContainerAwareCommand
18
{
19
    /**
20
     * {@inheritdoc}
21
     */
22 1
    protected function configure()
23
    {
24 1
        $this
25 1
            ->setName('adespresso:features:load')
26 1
            ->setDescription('Persist new features found in templates')
27 1
            ->addArgument(
28 1
                'path',
29 1
                InputArgument::REQUIRED | InputArgument::IS_ARRAY,
30
                'The path where to load the features'
31 1
            )
32 1
            ->addOption(
33 1
                'dry-run',
34 1
                null,
35 1
                InputOption::VALUE_NONE,
36
                'Do not persist new features'
37 1
            );
38 1
    }
39
40
    /**
41
     * {@inheritdoc}
42
     */
43 1
    public function execute(InputInterface $input, OutputInterface $output)
44
    {
45 1
        $container = $this->getContainer();
46 1
        $twig = $container->get('twig');
47 1
        $found = [];
48 1
        $files = Finder::create()
49 1
            ->files()
50 1
            ->name('*.twig');
51
52 1
        foreach ($input->getArgument('path') as $path) {
53 1
            $files = $files->in($path);
54 1
        }
55
56 1
        foreach ($files as $file) {
57
            $tree = $twig->parse(
58
                $twig->tokenize(file_get_contents($file->getPathname()))
59
            );
60
61
            if (!$tags = $this->findFeatureNodes($tree)) {
62
                continue;
63
            }
64
65
            $found += $tags;
66
67
            foreach ($tags as $tag) {
68
                $output->writeln(sprintf(
69
                    'Found <info>%s</info>.<info>%s</info> in <info>%s</info>',
70
                    $tag['parent'],
71
                    $tag['name'],
72
                    $file->getFilename()
73
                ));
74
            }
75 1
        }
76
77 1
        if ($input->getOption('dry-run')) {
78
            return;
79
        }
80
81 1
        $manager = $container->get('ae_feature.manager');
82 1
        foreach ($found as $tag) {
83
            $manager->findOrCreate($tag['name'], $tag['parent']);
84 1
        }
85 1
    }
86
87
    /**
88
     * Find feature nodes.
89
     *
90
     * @param Twig_Node $node
91
     *
92
     * @return array
93
     */
94 1
    private function findFeatureNodes(Twig_Node $node)
95
    {
96
        $found = [];
97
        $stack = [$node];
98
        while ($stack) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stack of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
99
            $node = array_pop($stack);
100
            if ($node instanceof FeatureNode) {
101 1
                $arguments = $node
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Twig_Node as the method getKeyValuePairs() does only exist in the following sub-classes of Twig_Node: Twig_Node_Expression_Array. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
102
                    ->getNode('tests')
103
                    ->getNode(0)
104
                    ->getNode('arguments')
105
                    ->getKeyValuePairs();
106
107
                $tag = [];
108
                foreach ($arguments as $argument) {
109
                    $keyAttr = $argument['key']->getAttribute('value');
110
                    $valueAttr = $argument['value']->getAttribute('value');
111
112
                    $tag[$keyAttr] = $valueAttr;
113
                }
114
                $key = md5(serialize($tag));
115
                $found[$key] = $tag;
116
            } else {
117
                foreach ($node as $child) {
118
                    if (null !== $child) {
119
                        $stack[] = $child;
120
                    }
121
                }
122
            }
123
        }
124
125
        return $found;
126
    }
127
}
128