Completed
Push — master ( 13f9fe...eefead )
by Paweł
16:36
created

AbstractRoleCommand::getAvailableUserTypes()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 1
nop 0
1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
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
namespace Sylius\Bundle\UserBundle\Command;
13
14
use Doctrine\Common\Persistence\ObjectManager;
15
use Sylius\Component\User\Model\UserInterface;
16
use Sylius\Component\User\Repository\UserRepositoryInterface;
17
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Output\OutputInterface;
20
use Symfony\Component\Console\Question\ChoiceQuestion;
21
use Symfony\Component\Console\Question\Question;
22
23
/**
24
 * @author Loïc Frémont <[email protected]>
25
 */
26
abstract class AbstractRoleCommand extends ContainerAwareCommand
27
{
28
    /**
29
     * {@inheritdoc}
30
     */
31
    protected function interact(InputInterface $input, OutputInterface $output)
32
    {
33
34
        // User types configured in the Bundle
35
        $availableUserTypes = $this->getAvailableUserTypes();
36
        if (empty($availableUserTypes)) {
37
            throw new \Exception(sprintf('At least one user type should implement %s', UserInterface::class));
38
        }
39
40
        if (!$input->getOption('user-type')) {
41
42
            // Do not ask if there's only 1 user type configured
43
            if (count($availableUserTypes) === 1) {
44
                $input->setOption('user-type', $availableUserTypes[0]);
45
            } else {
46
                $question = new ChoiceQuestion('Please enter the user type:', $availableUserTypes, 1);
47
                $question->setErrorMessage('Choice %s is invalid.');
48
                $userType = $this->getHelper('question')->ask($input, $output, $question);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method ask() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\QuestionHelper, Symfony\Component\Consol...r\SymfonyQuestionHelper.

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...
49
                $input->setOption('user-type', $userType);
50
            }
51
        }
52
53
        if (!$input->getArgument('email')) {
54
            $question = new Question('Please enter an email:');
55
            $question->setValidator(function ($email) {
56
                if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
57
                    throw new \RuntimeException("The email you entered is invalid.");
58
                }
59
                return $email;
60
            });
61
            $email = $this->getHelper('question')->ask($input, $output, $question);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method ask() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\QuestionHelper, Symfony\Component\Consol...r\SymfonyQuestionHelper.

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...
62
            $input->setArgument('email', $email);
63
        }
64
65
        if (!$input->getArgument('roles')) {
66
            $question = new Question('Please enter user\'s roles (separated by space):');
67
            $question->setValidator(function ($roles) {
68
                if (strlen($roles) < 1) {
69
                    throw new \RuntimeException("The value cannot be blank.");
70
                }
71
                return $roles;
72
            });
73
            $roles = $this->getHelper('question')->ask($input, $output, $question);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method ask() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\QuestionHelper, Symfony\Component\Consol...r\SymfonyQuestionHelper.

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...
74
75
            if (!empty($roles)) {
76
                $input->setArgument('roles', explode(' ', $roles));
0 ignored issues
show
Documentation introduced by
explode(' ', $roles) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
77
            }
78
        }
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84
    protected function execute(InputInterface $input, OutputInterface $output)
85
    {
86
        $email = $input->getArgument('email');
87
        $securityRoles = $input->getArgument('roles');
88
        $superAdmin = $input->getOption('super-admin');
89
        $userType = $input->getOption('user-type');
90
91
        if ($superAdmin) {
92
            $securityRoles[] = 'ROLE_ADMINISTRATION_ACCESS';
93
        }
94
95
        /** @var UserInterface $user */
96
        $user = $this->findUserByEmail($email, $userType);
97
98
        $this->executeRoleCommand($input, $output, $user, $securityRoles);
99
    }
100
101
    /**
102
     * @param string $email
103
     * @param        $userType
104
     *
105
     * @return UserInterface
106
     * @throws \InvalidArgumentException
107
     */
108
    protected function findUserByEmail($email, $userType)
109
    {
110
        /** @var UserInterface $user */
111
        $user = $this->getUserRepository($userType)->findOneByEmail($email);
112
113
        if (null === $user) {
114
            throw new \InvalidArgumentException(sprintf('Could not find user identified by email "%s"', $email));
115
        }
116
117
        return $user;
118
    }
119
120
    /**
121
     * @param $userType
122
     * @return ObjectManager
123
     */
124
    protected function getEntityManager($userType)
125
    {
126
        $class = $this->getUserModelClass($userType);
127
        return $this->getContainer()->get('doctrine')->getManagerForClass($class);
128
    }
129
130
    /**
131
     * @param $userType
132
     * @return UserRepositoryInterface
133
     */
134
    protected function getUserRepository($userType)
135
    {
136
        $class = $this->getUserModelClass($userType);
137
        return $this->getEntityManager($userType)->getRepository($class);
138
    }
139
140
    /**
141
     * @return array
142
     */
143
    protected function getAvailableUserTypes()
144
    {
145
        $config = $this->getContainer()->getParameter('sylius.user.users');
146
147
        // Keep only users types which implement \Sylius\Component\User\Model\UserInterface
148
        $userTypes = array_filter($config, function ($userTypeConfig) {
149
            return isset($userTypeConfig['user']['classes']['model']) && is_a($userTypeConfig['user']['classes']['model'], UserInterface::class, true);
150
        });
151
152
        return array_keys($userTypes);
153
    }
154
155
    /**
156
     * @param $userType
157
     * @return string
158
     * @throws \InvalidArgumentException
159
     */
160
    protected function getUserModelClass($userType)
161
    {
162
        $config = $this->getContainer()->getParameter('sylius.user.users');
163
        if (empty($config[$userType]['user']['classes']['model'])) {
164
            throw new \InvalidArgumentException(sprintf('User type %s misconfigured.', $userType));
165
        }
166
        return $config[$userType]['user']['classes']['model'];
167
    }
168
169
    /**
170
     * @param OutputInterface $output
171
     * @param UserInterface $user
172
     * @param array $securityRoles
173
     */
174
    abstract protected function executeRoleCommand(InputInterface $input, OutputInterface $output, UserInterface $user, array $securityRoles);
175
}
176