Completed
Push — master ( e8c3c5...e50b3f )
by Gaetano
15:09 queued 05:12
created

UserGroupManager   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 226
Duplicated Lines 5.75 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 88.04%

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 15
dl 13
loc 226
ccs 81
cts 92
cp 0.8804
rs 7.7
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
B create() 5 43 6
B update() 0 55 9
A delete() 0 12 2
D matchUserGroups() 0 31 10
D setReferences() 0 31 9
A setSection() 8 8 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\Executor;
4
5
use Kaliop\eZMigrationBundle\Core\Matcher\UserGroupMatcher;
6
use Kaliop\eZMigrationBundle\API\Collection\UserGroupCollection;
7
use Kaliop\eZMigrationBundle\Core\Matcher\RoleMatcher;
8
use Kaliop\eZMigrationBundle\Core\Matcher\SectionMatcher;
9
use eZ\Publish\API\Repository\Values\Content\Content;
10
11
/**
12
 * Handles user-group migrations.
13
 */
14
class UserGroupManager extends RepositoryExecutor
15
{
16
    protected $supportedStepTypes = array('user_group');
17 1
18
    protected $userGroupMatcher;
19 1
    protected $roleMatcher;
20
    protected $sectionMatcher;
21 1
22 1
    public function __construct(UserGroupMatcher $userGroupMatcher, RoleMatcher $roleMatcher, SectionMatcher $sectionMatcher)
23
    {
24
        $this->userGroupMatcher = $userGroupMatcher;
25
        $this->roleMatcher = $roleMatcher;
26 1
        $this->sectionMatcher = $sectionMatcher;
27
    }
28 1
29
    /**
30 1
     * Method to handle the create operation of the migration instructions
31 1
     */
32
    protected function create()
33 1
    {
34 1
        $userService = $this->repository->getUserService();
35 1
36
        $parentGroupId = $this->dsl['parent_group_id'];
37 1
        $parentGroupId = $this->referenceResolver->resolveReference($parentGroupId);
38
        $parentGroup = $this->userGroupMatcher->matchOneByKey($parentGroupId);
39 1
40 1
        $contentType = $this->repository->getContentTypeService()->loadContentTypeByIdentifier("user_group");
41 1
42 1
        $userGroupCreateStruct = $userService->newUserGroupCreateStruct($this->getLanguageCode(), $contentType);
43 1
        $userGroupCreateStruct->setField('name', $this->dsl['name']);
44 1
45
        if (isset($this->dsl['remote_id'])) {
46
            $userGroupCreateStruct->remoteId = $this->dsl['remote_id'];
47
        }
48 1
49 1
        if (isset($this->dsl['description'])) {
50 1
            $userGroupCreateStruct->setField('description', $this->dsl['description']);
51
        }
52
53
        if (isset($this->dsl['section'])) {
54
            $sectionKey = $this->referenceResolver->resolveReference($this->dsl['section']);
55 1
            $section = $this->sectionMatcher->matchOneByKey($sectionKey);
56 1
            $userGroupCreateStruct->sectionId = $section->id;
57
        }
58
59
        $userGroup = $userService->createUserGroup($userGroupCreateStruct, $parentGroup);
60
61
        if (isset($this->dsl['roles'])) {
62
            $roleService = $this->repository->getRoleService();
63 1
            // we support both Ids and Identifiers
64 View Code Duplication
            foreach ($this->dsl['roles'] as $roleId) {
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...
65 1
                $roleId = $this->referenceResolver->resolveReference($roleId);
66
                $role = $this->roleMatcher->matchOneByKey($roleId);
67
                $roleService->assignRoleToUserGroup($role, $userGroup);
68
            }
69 1
        }
70 1
71
        $this->setReferences($userGroup);
72 1
73 1
        return $userGroup;
74 1
    }
75 1
76
    /**
77 1
     * Method to handle the update operation of the migration instructions
78
     *
79
     * @throws \Exception When the ID of the user group is missing from the migration definition.
80 1
     */
81
    protected function update()
82
    {
83 1
        $userGroupCollection = $this->matchUserGroups('update');
84
85 1
        if (count($userGroupCollection) > 1 && isset($this->dsl['references'])) {
86 1
            throw new \Exception("Can not execute Group update because multiple groups match, and a references section is specified in the dsl. References can be set when only 1 group matches");
87 1
        }
88
89 1
        $userService = $this->repository->getUserService();
90 1
        $contentService = $this->repository->getContentService();
91 1
92
        foreach ($userGroupCollection as $key => $userGroup) {
0 ignored issues
show
Bug introduced by
The expression $userGroupCollection of type object<Kaliop\eZMigratio...erGroupCollection>|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...
93 1
94
            /** @var $updateStruct \eZ\Publish\API\Repository\Values\User\UserGroupUpdateStruct */
95 1
            $updateStruct = $userService->newUserGroupUpdateStruct();
96
97 1
            /** @var $contentUpdateStruct \eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct */
98 1
            $contentUpdateStruct = $contentService->newContentUpdateStruct();
99 1
100
            if (isset($this->dsl['name'])) {
101
                $contentUpdateStruct->setField('name', $this->dsl['name']);
102
            }
103 1
104
            if (isset($this->dsl['remote_id'])) {
105
                $contentUpdateStruct->remoteId = $this->dsl['remote_id'];
106 1
            }
107 1
108
            if (isset($this->dsl['description'])) {
109 1
                $contentUpdateStruct->setField('description', $this->dsl['description']);
110 1
            }
111
112
            $updateStruct->contentUpdateStruct = $contentUpdateStruct;
113
114
            $userGroup = $userService->updateUserGroup($userGroup, $updateStruct);
115
116
            if (isset($this->dsl['parent_group_id'])) {
117 1
                $parentGroupId = $this->dsl['parent_group_id'];
118
                $parentGroupId = $this->referenceResolver->resolveReference($parentGroupId);
119 1
                $newParentGroup = $this->userGroupMatcher->matchOneByKey($parentGroupId);
120
121
                // Move group to new parent
122
                $userService->moveUserGroup($userGroup, $newParentGroup);
123 1
            }
124
125
            if (isset($this->dsl['section'])) {
126
                $this->setSection($userGroup, $this->dsl['section']);
127 1
            }
128
129
            $userGroupCollection[$key] = $userGroup;
130 1
        }
131 1
132 1
        $this->setReferences($userGroupCollection);
0 ignored issues
show
Bug introduced by
It seems like $userGroupCollection defined by $this->matchUserGroups('update') on line 83 can be null; however, Kaliop\eZMigrationBundle...anager::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...
133 1
134
        return $userGroupCollection;
135 1
    }
136
137 1
    /**
138 1
     * Method to handle the delete operation of the migration instructions
139 1
     *
140
     * @throws \Exception When there are no groups specified for deletion.
141 1
     */
142 1
    protected function delete()
143 1
    {
144 1
        $userGroupCollection = $this->matchUserGroups('delete');
145
146
        $userService = $this->repository->getUserService();
147
148
        foreach ($userGroupCollection as $userGroup) {
0 ignored issues
show
Bug introduced by
The expression $userGroupCollection of type object<Kaliop\eZMigratio...erGroupCollection>|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...
149
            $userService->deleteUserGroup($userGroup);
150
        }
151
152
        return $userGroupCollection;
153 1
    }
154
155 1
    /**
156 1
     * @param string $action
157 1
     * @return UserGroupCollection
158
     * @throws \Exception
159 1
     */
160
    protected function matchUserGroups($action)
161 1
    {
162 1
        if (!isset($this->dsl['id']) && !isset($this->dsl['group']) && !isset($this->dsl['match'])) {
163 1
            throw new \Exception("The id  of a group or a match condition is required to $action it.");
164 1
        }
165 1
166 1
        // Backwards compat
167
        if (!isset($this->dsl['match'])) {
168
            if (isset($this->dsl['id'])) {
169 1
                $this->dsl['match']['id'] = $this->dsl['id'];
170
            }
171 1
            if (isset($this->dsl['group'])) {
172 1
                $this->dsl['match']['email'] = $this->dsl['group'];
173
            }
174 1
        }
175
176
        $match = $this->dsl['match'];
177
178
        // convert the references passed in the match
179
        foreach ($match as $condition => $values) {
180
            if (is_array($values)) {
181
                foreach ($values as $position => $value) {
182
                    $match[$condition][$position] = $this->referenceResolver->resolveReference($value);
183
                }
184
            } else {
185
                $match[$condition] = $this->referenceResolver->resolveReference($values);
186
            }
187
        }
188
189
        return $this->userGroupMatcher->match($match);
190
    }
191
192
    /**
193
     * Set references defined in the DSL for use in another step during the migrations.
194
     *
195
     * @throws \InvalidArgumentException When trying to set a reference to an unsupported attribute
196
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup|UserGroupCollection $userGroup
197
     * @return boolean
198
     */
199
    protected function setReferences($userGroup)
200
    {
201
        if (!array_key_exists('references', $this->dsl)) {
202
            return false;
203
        }
204
205
        if ($userGroup instanceof UserGroupCollection) {
206
            if (count($userGroup) > 1) {
207
                throw new \InvalidArgumentException('UserGroup Manager does not support setting references for creating/updating of multiple groups');
208
            }
209
            $userGroup = reset($userGroup);
210
        }
211
212
        foreach ($this->dsl['references'] as $reference) {
213
214
            switch ($reference['attribute']) {
215
                case 'object_id':
216
                case 'content_id':
217
                case 'user_group_id':
218
                case 'id':
219
                    $value = $userGroup->id;
220
                    break;
221
                default:
222
                    throw new \InvalidArgumentException('User Group Manager does not support setting references for attribute ' . $reference['attribute']);
223
            }
224
225
            $this->referenceResolver->addReference($reference['identifier'], $value);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Kaliop\eZMigrationBundle...erenceResolverInterface as the method addReference() does only exist in the following implementations of said interface: Kaliop\eZMigrationBundle...ver\ChainPrefixResolver, Kaliop\eZMigrationBundle...ver\ChainRegexpResolver, Kaliop\eZMigrationBundle...eResolver\ChainResolver, Kaliop\eZMigrationBundle...CustomReferenceResolver.

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...
226
        }
227
228
        return true;
229
    }
230
231 View Code Duplication
    protected function setSection(Content $content, $sectionKey)
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...
232
    {
233
        $sectionKey = $this->referenceResolver->resolveReference($sectionKey);
234
        $section = $this->sectionMatcher->matchOneByKey($sectionKey);
235
236
        $sectionService = $this->repository->getSectionService();
237
        $sectionService->assignSection($content->contentInfo, $section);
238
    }
239
}
240