Completed
Push — master ( 5a501b...3982a6 )
by Gaetano
08:04
created

RoleManager::generateMigration()   B

Complexity

Conditions 9
Paths 11

Size

Total Lines 70

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
dl 0
loc 70
ccs 0
cts 54
cp 0
rs 7.0989
c 0
b 0
f 0
cc 9
nc 11
nop 3
crap 90

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\Executor;
4
5
use eZ\Publish\API\Repository\Values\User\Role;
6
use eZ\Publish\API\Repository\RoleService;
7
use eZ\Publish\API\Repository\UserService;
8
use Kaliop\eZMigrationBundle\API\Collection\RoleCollection;
9
use Kaliop\eZMigrationBundle\API\MigrationGeneratorInterface;
10
use Kaliop\eZMigrationBundle\Core\Helper\LimitationConverter;
11
use Kaliop\eZMigrationBundle\Core\Matcher\RoleMatcher;
12
use eZ\Publish\API\Repository\Values\User\Limitation;
13
14
/**
15
 * Handles role migrations.
16
 */
17
class RoleManager extends RepositoryExecutor implements MigrationGeneratorInterface
18
{
19
    protected $supportedStepTypes = array('role');
20
21
    protected $limitationConverter;
22
    protected $roleMatcher;
23
24
    public function __construct(RoleMatcher $roleMatcher, LimitationConverter $limitationConverter)
25
    {
26
        $this->roleMatcher = $roleMatcher;
27
        $this->limitationConverter = $limitationConverter;
28
    }
29
30
    /**
31
     * Method to handle the create operation of the migration instructions
32
     */
33
    protected function create($step)
34
    {
35
        $roleService = $this->repository->getRoleService();
36
        $userService = $this->repository->getUserService();
37
38
        $roleCreateStruct = $roleService->newRoleCreateStruct($step->dsl['name']);
39
40
        // Publish new role
41
        $role = $roleService->createRole($roleCreateStruct);
42
        if (is_callable(array($roleService, 'publishRoleDraft'))) {
43
            $roleService->publishRoleDraft($role);
0 ignored issues
show
Bug introduced by
The method publishRoleDraft() does not seem to exist on object<eZ\Publish\API\Repository\RoleService>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
44
        }
45
46
        if (isset($step->dsl['policies'])) {
47
            foreach ($step->dsl['policies'] as $key => $ymlPolicy) {
48
                $this->addPolicy($role, $roleService, $ymlPolicy);
49
            }
50
        }
51
52
        if (isset($step->dsl['assign'])) {
53
            $this->assignRole($role, $roleService, $userService, $step->dsl['assign']);
54
        }
55
56
        $this->setReferences($role, $step);
0 ignored issues
show
Documentation introduced by
$role is of type object<eZ\Publish\API\Re...itory\Values\User\Role>, but the function expects a object<Object>|object<Ka...ion\AbstractCollection>.

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...
57
58
        return $role;
59
    }
60
61
    /**
62
     * Method to handle the update operation of the migration instructions
63
     */
64
    protected function update($step)
65
    {
66
        $roleCollection = $this->matchRoles('update', $step);
67
68
        if (count($roleCollection) > 1 && isset($step->dsl['references'])) {
69
            throw new \Exception("Can not execute Role update because multiple roles match, and a references section is specified in the dsl. References can be set when only 1 role matches");
70
        }
71
72
        if (count($roleCollection) > 1 && isset($step->dsl['new_name'])) {
73
            throw new \Exception("Can not execute Role update because multiple roles match, and a new_name is specified in the dsl.");
74
        }
75
76
        $roleService = $this->repository->getRoleService();
77
        $userService = $this->repository->getUserService();
78
79
        /** @var \eZ\Publish\API\Repository\Values\User\Role $role */
80
        foreach ($roleCollection as $key => $role) {
0 ignored issues
show
Bug introduced by
The expression $roleCollection of type object<Kaliop\eZMigratio...on\RoleCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
81
82
            // Updating role name
83
            if (isset($step->dsl['new_name'])) {
84
                $update = $roleService->newRoleUpdateStruct();
85
                $update->identifier = $step->dsl['new_name'];
86
                $role = $roleService->updateRole($role, $update);
87
            }
88
89
            if (isset($step->dsl['policies'])) {
90
                $ymlPolicies = $step->dsl['policies'];
91
92
                // Removing all policies so we can add them back.
93
                // TODO: Check and update policies instead of remove and add.
94
                $policies = $role->getPolicies();
95
                foreach ($policies as $policy) {
96
                    $roleService->deletePolicy($policy);
97
                }
98
99
                foreach ($ymlPolicies as $ymlPolicy) {
100
                    $this->addPolicy($role, $roleService, $ymlPolicy);
101
                }
102
            }
103
104
            if (isset($step->dsl['assign'])) {
105
                $this->assignRole($role, $roleService, $userService, $step->dsl['assign']);
106
            }
107
108
            $roleCollection[$key] = $role;
109
        }
110
111
        $this->setReferences($roleCollection, $step);
0 ignored issues
show
Bug introduced by
It seems like $roleCollection defined by $this->matchRoles('update', $step) on line 66 can be null; however, Kaliop\eZMigrationBundle...ecutor::setReferences() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
112
113
        return $roleCollection;
114
    }
115
116
    /**
117
     * Method to handle the delete operation of the migration instructions
118
     */
119
    protected function delete($step)
120
    {
121
        $roleCollection = $this->matchRoles('delete', $step);
122
123
        $this->setReferences($roleCollection, $step);
0 ignored issues
show
Bug introduced by
It seems like $roleCollection defined by $this->matchRoles('delete', $step) on line 121 can be null; however, Kaliop\eZMigrationBundle...ecutor::setReferences() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
124
125
        $roleService = $this->repository->getRoleService();
126
127
        foreach ($roleCollection as $role) {
0 ignored issues
show
Bug introduced by
The expression $roleCollection of type object<Kaliop\eZMigratio...on\RoleCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
128
            $roleService->deleteRole($role);
129
        }
130
131
        return $roleCollection;
132
    }
133
134
    /**
135
     * @param string $action
136
     * @return RoleCollection
137
     * @throws \Exception
138
     */
139 View Code Duplication
    protected function matchRoles($action, $step)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
140
    {
141
        if (!isset($step->dsl['name']) && !isset($step->dsl['match'])) {
142
            throw new \Exception("The name of a role or a match condition is required to $action it");
143
        }
144
145
        // Backwards compat
146
        if (isset($step->dsl['match'])) {
147
            $match = $step->dsl['match'];
148
        } else {
149
            $match = array('identifier' => $step->dsl['name']);
150
        }
151
152
        // convert the references passed in the match
153
        $match = $this->resolveReferencesRecursively($match);
154
155
        return $this->roleMatcher->match($match);
156
    }
157
158
    /**
159
     * @param Role $role
160
     * @param array $references the definitions of the references to set
161
     * @throws \InvalidArgumentException When trying to assign a reference to an unsupported attribute
162
     * @return array key: the reference names, values: the reference values
163
     */
164 View Code Duplication
    protected function getReferencesValues($role, array $references, $step)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
165
    {
166
        $refs = array();
167
168
        foreach ($references as $reference) {
169
            switch ($reference['attribute']) {
170
                case 'role_id':
171
                case 'id':
172
                    $value = $role->id;
173
                    break;
174
                case 'identifier':
175
                case 'role_identifier':
176
                    $value = $role->identifier;
177
                    break;
178
                default:
179
                    throw new \InvalidArgumentException('Role Manager does not support setting references for attribute ' . $reference['attribute']);
180
            }
181
182
            $refs[$reference['identifier']] = $value;
183
        }
184
185
        return $refs;
186
    }
187
188
    /**
189
     * @param array $matchCondition
190
     * @param string $mode
191
     * @param array $context
192
     * @throws \Exception
193
     * @return array
194
     */
195
    public function generateMigration(array $matchCondition, $mode, array $context = array())
196
    {
197
        $previousUserId = $this->loginUser($this->getAdminUserIdentifierFromContext($context));
198
        $roleCollection = $this->roleMatcher->match($matchCondition);
199
        $data = array();
200
201
        /** @var \eZ\Publish\API\Repository\Values\User\Role $role */
202
        foreach ($roleCollection as $role) {
0 ignored issues
show
Bug introduced by
The expression $roleCollection of type object<Kaliop\eZMigratio...on\RoleCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
203
            $roleData = array(
204
                'type' => reset($this->supportedStepTypes),
205
                'mode' => $mode
206
            );
207
208
            switch ($mode) {
209
                case 'create':
210
                    $roleData = array_merge(
211
                        $roleData,
212
                        array(
213
                            'name' => $role->identifier
214
                        )
215
                    );
216
                    break;
217
                case 'update':
218
                case 'delete':
219
                    $roleData = array_merge(
220
                        $roleData,
221
                        array(
222
                            'match' => array(
223
                                RoleMatcher::MATCH_ROLE_IDENTIFIER => $role->identifier
224
                            )
225
                        )
226
                    );
227
                    break;
228
                default:
229
                    throw new \Exception("Executor 'role' doesn't support mode '$mode'");
230
            }
231
232
            if ($mode != 'delete') {
233
                $policies = array();
234
                foreach ($role->getPolicies() as $policy) {
235
                    $limitations = array();
236
237
                    foreach ($policy->getLimitations() as $limitation) {
238
                        if (!($limitation instanceof Limitation)) {
239
                            throw new \Exception("The role contains an invalid limitation for policy {$policy->module}/{$policy->function}, we can not reliably generate its definition.");
240
                        }
241
                        $limitations[] = $this->limitationConverter->getLimitationArrayWithIdentifiers($limitation);
242
                    }
243
244
                    $policies[] = array(
245
                        'module' => $policy->module,
246
                        'function' => $policy->function,
247
                        'limitations' => $limitations
248
                    );
249
                }
250
251
                $roleData = array_merge(
252
                    $roleData,
253
                    array(
254
                        'policies' => $policies
255
                    )
256
                );
257
            }
258
259
            $data[] = $roleData;
260
        }
261
262
        $this->loginUser($previousUserId);
263
        return $data;
264
    }
265
266
    /**
267
     * Create a new Limitation object based on the type and value in the $limitation array.
268
     *
269
     * <pre>
270
     * $limitation = array(
271
     *  'identifier' => Type of the limitation
272
     *  'values' => array(Values to base the limitation on)
273
     * )
274
     * </pre>
275
     *
276
     * @param \eZ\Publish\API\Repository\RoleService $roleService
277
     * @param array $limitation
278
     * @return Limitation
279
     */
280
    protected function createLimitation(RoleService $roleService, array $limitation)
281
    {
282
        $limitationType = $roleService->getLimitationType($limitation['identifier']);
283
284
        $limitationValue = is_array($limitation['values']) ? $limitation['values'] : array($limitation['values']);
285
286
        foreach ($limitationValue as $id => $value) {
287
            $limitationValue[$id] = $this->referenceResolver->resolveReference($value);
288
        }
289
        $limitationValue = $this->limitationConverter->resolveLimitationValue($limitation['identifier'], $limitationValue);
290
        return $limitationType->buildValue($limitationValue);
291
    }
292
293
    /**
294
     * Assign a role to users and groups in the assignment array.
295
     *
296
     * <pre>
297
     * $assignments = array(
298
     *      array(
299
     *          'type' => 'user',
300
     *          'ids' => array(user ids),
301
     *          'limitation' => array(limitations)
302
     *      )
303
     * )
304
     * </pre>
305
     *
306
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
307
     * @param \eZ\Publish\API\Repository\RoleService $roleService
308
     * @param \eZ\Publish\API\Repository\UserService $userService
309
     * @param array $assignments
310
     */
311
    protected function assignRole(Role $role, RoleService $roleService, UserService $userService, array $assignments)
312
    {
313
        foreach ($assignments as $assign) {
314
            switch ($assign['type']) {
315 View Code Duplication
                case 'user':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
316
                    foreach ($assign['ids'] as $userId) {
317
                        $userId = $this->referenceResolver->resolveReference($userId);
318
319
                        $user = $userService->loadUser($userId);
320
321
                        if (!isset($assign['limitations'])) {
322
                            $roleService->assignRoleToUser($role, $user);
323
                        } else {
324
                            foreach ($assign['limitations'] as $limitation) {
325
                                $limitationObject = $this->createLimitation($roleService, $limitation);
326
                                $roleService->assignRoleToUser($role, $user, $limitationObject);
0 ignored issues
show
Documentation introduced by
$limitationObject is of type object<eZ\Publish\API\Re...Values\User\Limitation>, but the function expects a null|object<eZ\Publish\A...itation\RoleLimitation>.

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...
327
                            }
328
                        }
329
                    }
330
                    break;
331 View Code Duplication
                case 'group':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
332
                    foreach ($assign['ids'] as $groupId) {
333
                        $groupId = $this->referenceResolver->resolveReference($groupId);
334
335
                        $group = $userService->loadUserGroup($groupId);
336
337
                        if (!isset($assign['limitations'])) {
338
                            // q: why are we swallowing exceptions here ?
339
                            //try {
340
                                $roleService->assignRoleToUserGroup($role, $group);
341
                            //} catch (InvalidArgumentException $e) {}
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
342
                        } else {
343
                            foreach ($assign['limitations'] as $limitation) {
344
                                $limitationObject = $this->createLimitation($roleService, $limitation);
345
                                // q: why are we swallowing exceptions here ?
346
                                //try {
347
                                    $roleService->assignRoleToUserGroup($role, $group, $limitationObject);
0 ignored issues
show
Documentation introduced by
$limitationObject is of type object<eZ\Publish\API\Re...Values\User\Limitation>, but the function expects a null|object<eZ\Publish\A...itation\RoleLimitation>.

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...
348
                                //} catch (InvalidArgumentException $e) {}
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
349
                            }
350
                        }
351
                    }
352
                    break;
353
            }
354
        }
355
    }
356
357
    /**
358
     * Add new policies to the $role Role.
359
     *
360
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
361
     * @param \eZ\Publish\API\Repository\RoleService $roleService
362
     * @param array $policy
363
     */
364
    protected function addPolicy(Role $role, RoleService $roleService, array $policy)
365
    {
366
        $policyCreateStruct = $roleService->newPolicyCreateStruct($policy['module'], $policy['function']);
367
368
        if (array_key_exists('limitations', $policy)) {
369
            foreach ($policy['limitations'] as $limitation) {
370
                $limitationObject = $this->createLimitation($roleService, $limitation);
371
                $policyCreateStruct->addLimitation($limitationObject);
372
            }
373
        }
374
375
        $roleService->addPolicy($role, $policyCreateStruct);
376
    }
377
}
378