Completed
Push — master ( 178a08...5c0b6f )
by Oleg
05:22
created

SavePermissionsAction::process()   A

Complexity

Conditions 4
Paths 10

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 44
ccs 0
cts 41
cp 0
rs 9.216
c 0
b 0
f 0
cc 4
nc 10
nop 2
crap 20
1
<?php
2
declare(strict_types=1);
3
4
namespace SlayerBirden\DataFlowServer\Authorization\Controller;
5
6
use Doctrine\Common\Collections\Criteria;
7
use Doctrine\ORM\EntityManager;
8
use Doctrine\ORM\ORMException;
9
use Psr\Http\Message\ResponseInterface;
10
use Psr\Http\Message\ServerRequestInterface;
11
use Psr\Http\Server\MiddlewareInterface;
12
use Psr\Http\Server\RequestHandlerInterface;
13
use Psr\Log\LoggerInterface;
14
use SlayerBirden\DataFlowServer\Authorization\Entities\Permission;
15
use SlayerBirden\DataFlowServer\Authorization\HistoryManagementInterface;
16
use SlayerBirden\DataFlowServer\Doctrine\Middleware\ResourceMiddlewareInterface;
17
use SlayerBirden\DataFlowServer\Domain\Entities\ClaimedResourceInterface;
18
use SlayerBirden\DataFlowServer\Domain\Entities\User;
19
use SlayerBirden\DataFlowServer\Notification\DangerMessage;
20
use SlayerBirden\DataFlowServer\Notification\SuccessMessage;
21
use SlayerBirden\DataFlowServer\Stdlib\Validation\ValidationResponseFactory;
22
use Zend\Diactoros\Response\JsonResponse;
23
use Zend\Hydrator\HydratorInterface;
24
use Zend\InputFilter\InputFilterInterface;
25
26
class SavePermissionsAction implements MiddlewareInterface
27
{
28
    /**
29
     * @var EntityManager
30
     */
31
    private $entityManager;
32
    /**
33
     * @var LoggerInterface
34
     */
35
    private $logger;
36
    /**
37
     * @var InputFilterInterface
38
     */
39
    private $inputFilter;
40
    /**
41
     * @var HistoryManagementInterface
42
     */
43
    private $historyManagement;
44
    /**
45
     * @var HydratorInterface
46
     */
47
    private $hydrator;
48
49
    public function __construct(
50
        EntityManager $entityManager,
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $entityManager. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
51
        LoggerInterface $logger,
52
        InputFilterInterface $inputFilter,
53
        HistoryManagementInterface $historyManagement,
54
        HydratorInterface $hydrator
55
    ) {
56
        $this->entityManager = $entityManager;
57
        $this->logger = $logger;
58
        $this->inputFilter = $inputFilter;
59
        $this->historyManagement = $historyManagement;
60
        $this->hydrator = $hydrator;
61
    }
62
63
    /**
64
     * @inheritdoc
65
     */
66
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
67
    {
68
        $data = $request->getParsedBody();
69
        $user = $request->getAttribute(ResourceMiddlewareInterface::DATA_RESOURCE);
70
71
        $this->inputFilter->setData($data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $request->getParsedBody() on line 68 can also be of type null or object; however, Zend\InputFilter\InputFilterInterface::setData() does only seem to accept array|object<Traversable>, 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...
72
        if (!$this->inputFilter->isValid()) {
73
            return (new ValidationResponseFactory())('permissions', $this->inputFilter, []);
74
        }
75
        $this->entityManager->beginTransaction();
76
        try {
77
            $permissions = $this->processResources(
78
                $user,
79
                $data[ClaimedResourceInterface::OWNER_PARAM],
80
                ...$data['resources']
81
            );
82
            if (empty($permissions)) {
83
                $msg = new SuccessMessage('No changes detected. The input is identical to the storage.');
84
            } else {
85
                $this->entityManager->flush();
86
                $this->entityManager->commit();
87
                $msg = new SuccessMessage('Successfully set permissions to resources.');
88
            }
89
90
            return new JsonResponse([
91
                'msg' => $msg,
92
                'data' => [
93
                    'permissions' => array_map([$this->hydrator, 'extract'], $permissions),
94
                ],
95
                'success' => true,
96
            ], 200);
97
        } catch (ORMException $exception) {
98
            $this->logger->error((string)$exception);
99
            $this->entityManager->rollback();
100
101
            return new JsonResponse([
102
                'msg' => new DangerMessage('There was an error while setting the permissions.'),
103
                'data' => [
104
                    'permissions' => [],
105
                ],
106
                'success' => false,
107
            ], 400);
108
        }
109
    }
110
111
    /**
112
     * @param User $user
113
     * @param User $owner
114
     * @param string ...$resources
115
     * @return Permission[]
116
     * @throws ORMException
117
     */
118
    private function processResources(User $user, User $owner, string ...$resources): array
119
    {
120
        $result = [];
121
        $collection = $this->entityManager
122
            ->getRepository(Permission::class)
123
            ->matching(Criteria::create()->where(Criteria::expr()->eq('user', $user)));
124
125
        $currentResources = array_map(function (Permission $permission) {
126
            return $permission->getResource();
127
        }, $collection->toArray());
128
129
        $toRemove = array_diff($currentResources, $resources);
130
        $toAdd = array_diff($resources, $currentResources);
131
132
        if (empty($toAdd) && empty($toRemove)) {
133
            return [];
134
        }
135
136
        $this->processItemsToRemove($collection, $toRemove, $owner, $result);
137
        $this->processItemsToAdd($toAdd, $user, $owner, $result);
138
139
        return $result;
140
    }
141
142
    /**
143
     * @param $collection
144
     * @param $toRemove
145
     * @param $owner
146
     * @param $result
147
     * @throws ORMException
148
     */
149
    private function processItemsToRemove($collection, $toRemove, $owner, &$result): void
150
    {
151
        /** @var Permission $permission */
152
        foreach ($collection as $permission) {
153
            if (in_array($permission->getResource(), $toRemove, true)) {
154
                $this->entityManager->remove($permission);
155
                $history = $this->historyManagement->fromPermission($permission);
156
                $history->setOwner($owner);
157
                $this->entityManager->persist($history);
158
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
159
            } else {
160
                $result[] = $permission;
161
            }
162
        }
163
    }
164
165
    /**
166
     * @param $toAdd
167
     * @param $user
168
     * @param $owner
169
     * @param $result
170
     * @throws ORMException
171
     */
172
    private function processItemsToAdd($toAdd, $user, $owner, &$result): void
173
    {
174
        foreach ($toAdd as $resource) {
175
            $permission = new Permission();
176
            $permission->setResource($resource);
177
            $permission->setUser($user);
178
            $result[] = $permission;
179
            $this->entityManager->persist($permission);
180
            $history = $this->historyManagement->fromPermission($permission);
181
            $history->setOwner($owner);
182
            $this->entityManager->persist($history);
183
        }
184
    }
185
}
186