Completed
Pull Request — master (#91)
by
unknown
06:19
created

UserGroupManager::update()   C

Complexity

Conditions 7
Paths 10

Size

Total Lines 48
Code Lines 23

Duplication

Lines 9
Ratio 18.75 %

Code Coverage

Tests 26
CRAP Score 7.0024

Importance

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