Passed
Push — master ( f2e1d1...eea237 )
by Divine Niiquaye
03:30
created

EntityUserProvider::supportsClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 1
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 6
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of DivineNii opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 DivineNii (https://divinenii.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Rade\Database\Doctrine\Provider;
19
20
use Doctrine\Persistence\Mapping\ClassMetadata;
0 ignored issues
show
Bug introduced by
The type Doctrine\Persistence\Mapping\ClassMetadata was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
21
use Doctrine\Persistence\ObjectManager;
0 ignored issues
show
Bug introduced by
The type Doctrine\Persistence\ObjectManager was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use Doctrine\Persistence\ObjectRepository;
0 ignored issues
show
Bug introduced by
The type Doctrine\Persistence\ObjectRepository was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
23
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
24
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
25
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
26
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
27
use Symfony\Component\Security\Core\User\UserInterface;
28
use Symfony\Component\Security\Core\User\UserProviderInterface;
29
30
/**
31
 * Wrapper around a Doctrine ObjectManager.
32
 *
33
 * Provides provisioning for Doctrine entity users.
34
 *
35
 * @author Fabien Potencier <[email protected]>
36
 * @author Johannes M. Schmitt <[email protected]>
37
 */
38
class EntityUserProvider implements UserProviderInterface, PasswordUpgraderInterface
39
{
40
    private ObjectManager $objectManager;
41
    private string $classOrAlias;
42
    private ?string $property;
43
    private ?string $class = null;
44
45
    public function __construct(ObjectManager $objectManager, string $classOrAlias, string $property = 'username')
46
    {
47
        $this->objectManager = $objectManager;
48
        $this->classOrAlias = $classOrAlias;
49
        $this->property = $property;
50
    }
51
52
    public function loadUserByIdentifier(string $identifier): UserInterface
53
    {
54
        $repository = $this->getRepository();
55
56
        if ($repository instanceof UserLoaderInterface) {
57
            $user = $repository->loadUserByIdentifier($identifier);
58
        } elseif (null === $property = $this->property) {
59
            throw new \InvalidArgumentException(\sprintf('You must either make the "%s" entity Doctrine Repository ("%s") implement "Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface" or set the "property" option in the corresponding entity provider configuration.', $this->classOrAlias, \get_debug_type($repository)));
60
        }
61
62
        if (null === $user = ($user ?? $repository->findOneBy([$property => $identifier]))) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $property does not seem to be defined for all execution paths leading up to this point.
Loading history...
63
            $e = new UserNotFoundException(\sprintf('User "%s" not found.', $identifier));
64
            $e->setUserIdentifier($identifier);
65
66
            throw $e;
67
        }
68
69
        return $user;
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function refreshUser(UserInterface $user): UserInterface
76
    {
77
        $class = $this->getClass();
78
79
        if (!$user instanceof $class) {
80
            throw new UnsupportedUserException(\sprintf('Instances of "%s" are not supported.', \get_debug_type($user)));
81
        }
82
83
        $repository = $this->getRepository();
84
85
        if ($repository instanceof UserProviderInterface) {
86
            $refreshedUser = $repository->refreshUser($user);
87
        } else {
88
            // The user must be reloaded via the primary key as all other data
89
            // might have changed without proper persistence in the database.
90
            // That's the case when the user has been changed by a form with
91
            // validation errors.
92
            if (!$id = $this->getClassMetadata()->getIdentifierValues($user)) {
93
                throw new \InvalidArgumentException('You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine.');
94
            }
95
96
            $refreshedUser = $repository->find($id);
97
98
            if (null === $refreshedUser) {
99
                $e = new UserNotFoundException('User with id ' . \json_encode($id) . ' not found.');
100
                $e->setUserIdentifier(\json_encode($id));
101
102
                throw $e;
103
            }
104
        }
105
106
        return $refreshedUser;
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112
    public function supportsClass(string $class): bool
113
    {
114
        return $class === $this->getClass() || \is_subclass_of($class, $this->getClass());
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     *
120
     * @final
121
     */
122
    public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
123
    {
124
        $class = $this->getClass();
125
126
        if (!$user instanceof $class) {
127
            throw new UnsupportedUserException(\sprintf('Instances of "%s" are not supported.', \get_debug_type($user)));
128
        }
129
130
        $repository = $this->getRepository();
131
132
        if ($repository instanceof PasswordUpgraderInterface) {
133
            $repository->upgradePassword($user, $newHashedPassword);
134
        }
135
    }
136
137
    private function getRepository(): ObjectRepository
138
    {
139
        return $this->objectManager->getRepository($this->classOrAlias);
140
    }
141
142
    private function getClass(): string
143
    {
144
        if (!isset($this->class)) {
145
            $class = $this->classOrAlias;
146
147
            if (\str_contains($class, ':')) {
148
                $class = $this->getClassMetadata()->getName();
149
            }
150
151
            $this->class = $class;
152
        }
153
154
        return $this->class;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->class could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
155
    }
156
157
    private function getClassMetadata(): ClassMetadata
158
    {
159
        return $this->objectManager->getClassMetadata($this->classOrAlias);
160
    }
161
}
162