Completed
Push — ezp25366-copy_content_relation... ( c42473...4b3f28 )
by
unknown
56:41 queued 26:30
created

RelationProcessor::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 6
rs 9.4285
cc 1
eloc 4
nc 1
nop 3
1
<?php
2
3
/**
4
 * File containing the RelationProcessor class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 *
9
 * @version //autogentag//
10
 */
11
namespace eZ\Publish\Core\Repository\Helper;
12
13
use eZ\Publish\SPI\Persistence\Content\VersionInfo as SPIVersionInfo;
14
use eZ\Publish\SPI\Persistence\Handler;
15
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
16
use eZ\Publish\Core\Repository\Values\Content\Relation;
17
use eZ\Publish\Core\FieldType\Value as BaseValue;
18
use eZ\Publish\SPI\FieldType\FieldType as SPIFieldType;
19
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as SPIRelationCreateStruct;
20
use eZ\Publish\SPI\Persistence\Content\Relation as SPIRelation;
21
22
/**
23
 * RelationProcessor is an internal service used for handling field relations upon Content creation or update.
24
 *
25
 * @internal
26
 */
27
class RelationProcessor
28
{
29
    /**
30
     * @var \eZ\Publish\SPI\Persistence\Handler
31
     */
32
    protected $persistenceHandler;
33
34
    /**
35
     * @var \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry
36
     */
37
    private $fieldTypeRegistry;
38
39
    /**
40
     * @var \eZ\Publish\Core\Repository\Helper\DomainMapper
41
     */
42
    private $domainMapper;
43
44
    /**
45
     * Setups service with reference to repository object that created it & corresponding handler.
46
     *
47
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
48
     * @param \eZ\Publish\Core\Repository\Helper\FieldTypeRegistry $fieldTypeRegistry
49
     */
50
    public function __construct(Handler $handler, FieldTypeRegistry $fieldTypeRegistry, DomainMapper $domainMapper)
51
    {
52
        $this->persistenceHandler = $handler;
53
        $this->fieldTypeRegistry = $fieldTypeRegistry;
54
        $this->domainMapper = $domainMapper;
55
    }
56
57
    /**
58
     * Appends destination Content ids of given $fieldValue to the $relation array.
59
     *
60
     * If $fieldValue contains Location ids, the will be converted to the Content id that Location encapsulates.
61
     *
62
     * @param array $relations
63
     * @param array $locationIdToContentIdMapping An array with Location Ids as keys and corresponding Content Id as values
64
     * @param \eZ\Publish\SPI\FieldType\FieldType $fieldType
65
     * @param \eZ\Publish\Core\FieldType\Value $fieldValue Accepted field value.
66
     * @param string $fieldDefinitionId
67
     */
68
    public function appendFieldRelations(
69
        array &$relations,
70
        array &$locationIdToContentIdMapping,
71
        SPIFieldType $fieldType,
72
        BaseValue $fieldValue,
73
        $fieldDefinitionId
74
    ) {
75
        foreach ($fieldType->getRelations($fieldValue) as $relationType => $destinationIds) {
76
            if ($relationType === Relation::FIELD) {
77
                if (!isset($relations[$relationType][$fieldDefinitionId])) {
78
                    $relations[$relationType][$fieldDefinitionId] = array();
79
                }
80
                $relations[$relationType][$fieldDefinitionId] += array_flip($destinationIds);
81
            } elseif ($relationType & (Relation::LINK | Relation::EMBED)) {
82
                // Using bitwise operators as Legacy Stack stores COMMON, LINK and EMBED relation types
83
                // in the same entry using bitmask
84
                if (!isset($relations[$relationType])) {
85
                    $relations[$relationType] = array();
86
                }
87
88
                if (isset($destinationIds['locationIds'])) {
89
                    foreach ($destinationIds['locationIds'] as $locationId) {
90
                        if (!isset($locationIdToContentIdMapping[$locationId])) {
91
                            $location = $this->persistenceHandler->locationHandler()->load($locationId);
92
                            $locationIdToContentIdMapping[$locationId] = $location->contentId;
93
                        }
94
95
                        $relations[$relationType][$locationIdToContentIdMapping[$locationId]] = true;
96
                    }
97
                }
98
99
                if (isset($destinationIds['contentIds'])) {
100
                    $relations[$relationType] += array_flip($destinationIds['contentIds']);
101
                }
102
            }
103
        }
104
    }
105
106
    /**
107
     * Persists relation data for a content version.
108
     *
109
     * This method creates new relations and deletes removed relations.
110
     *
111
     * @param array $inputRelations
112
     * @param mixed $sourceContentId
113
     * @param mixed $sourceContentVersionNo
114
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
115
     * @param \eZ\Publish\API\Repository\Values\Content\Relation[] $existingRelations An array of existing relations for Content version (empty when creating new content)
116
     */
117
    public function processFieldRelations(
118
        array $inputRelations,
119
        $sourceContentId,
120
        $sourceContentVersionNo,
121
        ContentType $contentType,
122
        array $existingRelations = array()
123
    ) {
124
        // Map existing relations for easier handling
125
        $mappedRelations = array();
126
        foreach ($existingRelations as $relation) {
127
            if ($relation->type === Relation::FIELD) {
128
                $fieldDefinitionId = $contentType->getFieldDefinition($relation->sourceFieldDefinitionIdentifier)->id;
129
                $mappedRelations[$relation->type][$fieldDefinitionId][$relation->destinationContentInfo->id] = $relation;
130
            }
131
            // Using bitwise AND as Legacy Stack stores COMMON, LINK and EMBED relation types
132
            // in the same entry using bitmask
133 View Code Duplication
            if ($relation->type & Relation::LINK) {
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...
134
                $mappedRelations[Relation::LINK][$relation->destinationContentInfo->id] = $relation;
135
            }
136 View Code Duplication
            if ($relation->type & Relation::EMBED) {
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...
137
                $mappedRelations[Relation::EMBED][$relation->destinationContentInfo->id] = $relation;
138
            }
139
        }
140
141
        // Add new relations
142
        foreach ($inputRelations as $relationType => $relationData) {
143
            if ($relationType === Relation::FIELD) {
144
                foreach ($relationData as $fieldDefinitionId => $contentIds) {
145
                    foreach (array_keys($contentIds) as $destinationContentId) {
146
                        if (isset($mappedRelations[$relationType][$fieldDefinitionId][$destinationContentId])) {
147
                            unset($mappedRelations[$relationType][$fieldDefinitionId][$destinationContentId]);
148 View Code Duplication
                        } else {
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...
149
                            $this->persistenceHandler->contentHandler()->addRelation(
150
                                new SPIRelationCreateStruct(
151
                                    array(
152
                                        'sourceContentId' => $sourceContentId,
153
                                        'sourceContentVersionNo' => $sourceContentVersionNo,
154
                                        'sourceFieldDefinitionId' => $fieldDefinitionId,
155
                                        'destinationContentId' => $destinationContentId,
156
                                        'type' => $relationType,
157
                                    )
158
                                )
159
                            );
160
                        }
161
                    }
162
                }
163
            } elseif ($relationType === Relation::LINK || $relationType === Relation::EMBED) {
164
                foreach (array_keys($relationData) as $destinationContentId) {
165
                    if (isset($mappedRelations[$relationType][$destinationContentId])) {
166
                        unset($mappedRelations[$relationType][$destinationContentId]);
167 View Code Duplication
                    } else {
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...
168
                        $this->persistenceHandler->contentHandler()->addRelation(
169
                            new SPIRelationCreateStruct(
170
                                array(
171
                                    'sourceContentId' => $sourceContentId,
172
                                    'sourceContentVersionNo' => $sourceContentVersionNo,
173
                                    'sourceFieldDefinitionId' => null,
174
                                    'destinationContentId' => $destinationContentId,
175
                                    'type' => $relationType,
176
                                )
177
                            )
178
                        );
179
                    }
180
                }
181
            }
182
        }
183
184
        // Remove relations not present in input set
185
        foreach ($mappedRelations as $relationType => $relationData) {
186
            foreach ($relationData as $relationEntry) {
187
                switch ($relationType) {
188
                    case Relation::FIELD:
189
                        foreach ($relationEntry as $relation) {
190
                            $this->persistenceHandler->contentHandler()->removeRelation(
191
                                $relation->id,
192
                                $relationType
193
                            );
194
                        }
195
                        break;
196
                    case Relation::LINK:
197
                    case Relation::EMBED:
198
                        $this->persistenceHandler->contentHandler()->removeRelation(
199
                            $relationEntry->id,
200
                            $relationType
201
                        );
202
                }
203
            }
204
        }
205
    }
206
207
    /**
208
     * Updates the relations for $contentId, either for $versionNo, or for all versions if not set.
209
     *
210
     * @param mixed $contentId
211
     * @param int $versionNo
212
     *
213
     * @return void
214
     */
215
    public function updateContentRelations($contentId, ContentType $contentType, $versionNo = null)
216
    {
217
        $contentHandler = $this->persistenceHandler->contentHandler();
218
        if (!isset($versionNo)) {
219
            $versions = $contentHandler->listVersions($contentId);
220
        } else {
221
            $versions = [$contentHandler->loadVersionInfo($contentId, $versionNo)];
222
        }
223
224
        foreach ($versions as $versionInfo) {
225
            $locationIdToContentIdMapping = $existingRelations = $relations = [];
0 ignored issues
show
Unused Code introduced by
$existingRelations is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
226
            foreach ($contentHandler->load($contentId, $versionInfo->versionNo)->fields as $field) {
227
                $fieldType = $this->fieldTypeRegistry->getFieldType($field->type);
228
                $this->appendFieldRelations(
229
                    $relations,
230
                    $locationIdToContentIdMapping,
231
                    $fieldType,
232
                    $fieldType->fromPersistenceValue($field->value),
0 ignored issues
show
Compatibility introduced by
$fieldType->fromPersistenceValue($field->value) of type object<eZ\Publish\SPI\FieldType\Value> is not a sub-type of object<eZ\Publish\Core\FieldType\Value>. It seems like you assume a concrete implementation of the interface eZ\Publish\SPI\FieldType\Value to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
233
                    $field->fieldDefinitionId
234
                );
235
            }
236
            $this->processFieldRelations(
237
                $relations,
238
                $contentId,
239
                $versionInfo->versionNo,
240
                $contentType,
241
                $this->loadExistingRelations($versionInfo)
242
            );
243
        }
244
    }
245
246
    private function loadExistingRelations(SPIVersionInfo $versionInfo)
247
    {
248
        return array_map(
249
            function(SPIRelation $spiRelation) use ($versionInfo) {
250
                return $this->domainMapper->buildRelationDomainObject(
251
                    $spiRelation,
252
                    $versionInfo->contentInfo,
0 ignored issues
show
Documentation introduced by
$versionInfo->contentInfo is of type object<eZ\Publish\SPI\Pe...ce\Content\ContentInfo>, but the function expects a object<eZ\Publish\API\Re...es\Content\ContentInfo>.

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...
253
                    $this->domainMapper->buildContentInfoDomainObject(
254
                        $this->persistenceHandler->contentHandler()->loadContentInfo($spiRelation->destinationContentId)
255
                    )
256
                );
257
            },
258
            $this->persistenceHandler->contentHandler()->loadRelations(
259
                $versionInfo->contentInfo->id,
260
                $versionInfo->versionNo,
261
                Relation::COMMON|Relation::EMBED
262
            )
263
        );
264
    }
265
}
266