Completed
Pull Request — master (#90)
by Andreas
10:43
created

UserGroupManager   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 207
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 88.04%

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 12
dl 0
loc 207
ccs 81
cts 92
cp 0.8804
rs 8.8
c 0
b 0
f 0

6 Methods

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