Completed
Pull Request — 1.0 (#53)
by Harald
09:44 queued 04:24
created

ContentManager::find()   C

Complexity

Conditions 8
Paths 24

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 8.1867

Importance

Changes 6
Bugs 0 Features 1
Metric Value
c 6
b 0
f 1
dl 0
loc 33
ccs 18
cts 21
cp 0.8571
rs 5.3846
cc 8
eloc 20
nc 24
nop 2
crap 8.1867
1
<?php
2
3
/*
4
 * This file is part of Transfer.
5
 *
6
 * For the full copyright and license information, please view the LICENSE file located
7
 * in the root directory.
8
 */
9
10
namespace Transfer\EzPlatform\Repository\Manager;
11
12
use eZ\Publish\API\Repository\ContentService;
13
use eZ\Publish\API\Repository\ContentTypeService;
14
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
15
use eZ\Publish\API\Repository\LocationService;
16
use eZ\Publish\API\Repository\Repository;
17
use eZ\Publish\API\Repository\Values\Content\Content;
18
use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct;
19
use eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct;
20
use eZ\Publish\API\Repository\Values\Content\Location;
21
use Psr\Log\LoggerAwareInterface;
22
use Psr\Log\LoggerInterface;
23
use Transfer\Data\ObjectInterface;
24
use Transfer\Data\ValueObject;
25
use Transfer\EzPlatform\Repository\Values\ContentObject;
26
use Transfer\EzPlatform\Repository\Values\LocationObject;
27
use Transfer\EzPlatform\Exception\MissingIdentificationPropertyException;
28
use Transfer\EzPlatform\Exception\UnsupportedObjectOperationException;
29
use Transfer\EzPlatform\Repository\Manager\Type\CreatorInterface;
30
use Transfer\EzPlatform\Repository\Manager\Type\FinderInterface;
31
use Transfer\EzPlatform\Repository\Manager\Type\RemoverInterface;
32
use Transfer\EzPlatform\Repository\Manager\Type\UpdaterInterface;
33
34
/**
35
 * Content manager.
36
 *
37
 * @internal
38
 */
39
class ContentManager implements LoggerAwareInterface, CreatorInterface, UpdaterInterface, RemoverInterface, FinderInterface
40
{
41
    /**
42
     * @var LocationManager
43
     */
44
    private $locationManager;
45
46
    /**
47
     * @var ContentService
48
     */
49
    protected $contentService;
50
51
    /**
52
     * @var ContentTypeService
53
     */
54
    protected $contentTypeService;
55
56
    /**
57
     * @var LocationService
58
     */
59
    protected $locationService;
60
61
    /**
62
     * @var LoggerInterface
63
     */
64
    protected $logger;
65
66
    /**
67
     * @param Repository      $repository
68
     * @param LocationManager $locationManager
69
     */
70 9
    public function __construct(Repository $repository, LocationManager $locationManager)
71
    {
72 9
        $this->locationManager = $locationManager;
73 9
        $this->contentService = $repository->getContentService();
74 9
        $this->contentTypeService = $repository->getContentTypeService();
75 9
        $this->locationService = $repository->getLocationService();
76 9
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81 9
    public function setLogger(LoggerInterface $logger)
82
    {
83 9
        $this->logger = $logger;
84 9
    }
85
86
    /**
87
     * Finds a content object by content ID or remote ID.
88
     * Returns ContentObject with populated properties, or false|NotFoundException.
89
     *
90
     * @param ValueObject|ContentObject $object
91
     * @param bool                      $throwException
92
     *
93
     * @return false|ContentObject
94
     *
95
     * @throws NotFoundException
96
     */
97 11
    public function find(ValueObject $object, $throwException = false)
98
    {
99
        try {
100 11
            if ($object->getProperty('remote_id')) {
101 11
                $content = $this->contentService->loadContentByRemoteId($object->getProperty('remote_id'));
102 8
            } elseif ($object->getProperty('id')) {
103
                $content = $this->contentService->loadContent($object->getProperty('id'));
104
            }
105 11
        } catch (NotFoundException $notFoundException) {
106 6
            $exception = $notFoundException;
107
        }
108
109 11
        if (!isset($content)) {
110 6
            if (isset($exception) && $throwException) {
111
                throw $exception;
112
            }
113
114 6
            return false;
115
        }
116
117 8
        $object = new ContentObject(array());
118 8
        $object->getMapper()->contentToObject($content);
119
120 8
        if ($content->contentInfo->published) {
121 8
            $locations = $this->locationService->loadLocations($content->contentInfo);
122 8
            $object->setParentLocations($locations);
123 8
        }
124
125 8
        $type = $this->contentTypeService->loadContentType($content->contentInfo->contentTypeId);
126 8
        $object->setProperty('content_type_identifier', $type->identifier);
127
128 8
        return $object;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $object; (Transfer\EzPlatform\Repo...ry\Values\ContentObject) is incompatible with the return type declared by the interface Transfer\EzPlatform\Repo...e\FinderInterface::find of type eZ\Publish\API\Repository\Values\ValueObject|false.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 6
    public function create(ObjectInterface $object)
135
    {
136 6
        if (!$object instanceof ContentObject) {
137
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
138
        }
139
140 6
        $createStruct = $this->contentService->newContentCreateStruct(
141 6
            $this->contentTypeService->loadContentTypeByIdentifier($object->getProperty('content_type_identifier')),
142 6
            $object->getProperty('language')
143 6
        );
144
145 6
        $this->mapObjectToContentStruct($object, $createStruct);
146
147
        /** @var LocationObject[] $locationObjects */
148 6
        $locationObjects = $object->getProperty('parent_locations');
149 6
        $locationCreateStructs = [];
150 6
        if (is_array($locationObjects) && count($locationObjects) > 0) {
151 2
            foreach ($locationObjects as $locationObject) {
152 2
                $locationCreateStruct = $this->locationService->newLocationCreateStruct($locationObject->data['parent_location_id']);
153 2
                $locationObject->getMapper()->getNewLocationCreateStruct($locationCreateStruct);
154 2
                $locationCreateStructs[] = $locationCreateStruct;
155 2
            }
156 2
        }
157
158 6
        $content = $this->contentService->createContent($createStruct, $locationCreateStructs);
159 6
        $this->contentService->publishVersion($content->versionInfo);
160
161 6
        if ($this->logger) {
162 5
            $this->logger->info(sprintf('Published new version of %s', $object->getProperty('name')), array('ContentManager::create'));
163 5
        }
164
165 6
        $object->setProperty('id', $content->contentInfo->id);
166 6
        $object->setProperty('version_info', $content->versionInfo);
167 6
        $object->setProperty('content_info', $content->contentInfo);
168
169 6
        return $object;
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175 11
    public function update(ObjectInterface $object)
176 8
    {
177 7
        if (!$object instanceof ContentObject) {
178
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
179 11
        }
180
181 7
        $existingContent = $this->find($object);
182 7
        if (null === $object->getProperty('content_info')) {
183 6
            $object->setProperty('content_info', $existingContent->getProperty('content_info'));
184 6
        }
185
186 7
        $contentDraft = $this->contentService->createContentDraft($object->getProperty('content_info'));
187
188 7
        $contentUpdateStruct = $this->contentService->newContentUpdateStruct();
189 7
        $this->mapObjectToUpdateStruct($object, $contentUpdateStruct);
190
191 7
        $contentDraft = $this->contentService->updateContent($contentDraft->versionInfo, $contentUpdateStruct);
192 7
        $content = $this->contentService->publishVersion($contentDraft->versionInfo);
193
194 7
        if ($this->logger) {
195 4
            $this->logger->info(sprintf('Published new version of %s', $object->getProperty('name')), array('ContentManager::update'));
196 4
        }
197
198 7
        $object->setProperty('id', $content->contentInfo->id);
199 7
        $object->setProperty('version_info', $content->versionInfo);
200 7
        $object->setProperty('content_info', $content->contentInfo);
201
202
        // Add/Update/Delete parent locations
203 7
        $this->locationManager->syncronizeLocationsFromContentObject($object);
204
205 7
        return $object;
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211 11
    public function createOrUpdate(ObjectInterface $object)
212
    {
213 11
        if (!$object instanceof ContentObject) {
214
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
215
        }
216
217 11
        if (!$object->getProperty('content_id') && !$object->getProperty('remote_id')) {
218 1
            throw new MissingIdentificationPropertyException($object);
219
        }
220
221 10
        if ($this->find($object)) {
222 7
            return $this->update($object);
223 1
        } else {
224 6
            return $this->create($object);
225
        }
226
    }
227
228
    /**
229
     * {@inheritdoc}
230
     */
231 1
    public function remove(ObjectInterface $object)
232
    {
233 1
        if (!$object instanceof ContentObject) {
234
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
235
        }
236
237 1
        $object = $this->find($object);
238
239 1
        if ($object instanceof ContentObject && $object->getProperty('content_info')) {
240 1
            $this->contentService->deleteContent($object->getProperty('content_info'));
241
242 1
            return true;
243
        }
244
245
        return false;
246
    }
247
248
    /**
249
     * Assigns a main location ID for a content object.
250
     *
251
     * @param ContentObject $object   Content object
252
     * @param Location      $location Location
253
     *
254
     * @return Content
255
     */
256 8
    public function setMainLocation(ContentObject $object, Location $location)
257
    {
258 8
        $contentMetadataUpdateStruct = $this->contentService->newContentMetadataUpdateStruct();
259
260 2
        $contentMetadataUpdateStruct->mainLocationId = $location->id;
261
262 2
        $object->setProperty('main_location_id', $location->id);
263
264 2
        return $this->contentService->updateContentMetadata($object->getProperty('content_info'), $contentMetadataUpdateStruct);
265
    }
266
267
    /**
268
     * Maps object data to create struct.
269
     *
270
     * @param ContentObject       $object       Content object to map from
271
     * @param ContentCreateStruct $createStruct Content create struct to map to
272
     *
273
     * @throws \InvalidArgumentException
274
     */
275 6
    private function mapObjectToContentStruct(ContentObject $object, ContentCreateStruct $createStruct)
276
    {
277 6
        $this->assignStructFieldValues($object, $createStruct);
278
279 6
        if ($object->getProperty('language')) {
280 6
            $createStruct->mainLanguageCode = $object->getProperty('language');
281 6
        }
282
283 6
        if ($object->getProperty('remote_id')) {
284 6
            $createStruct->remoteId = $object->getProperty('remote_id');
285 6
        }
286 6
    }
287
288
    /**
289
     * Maps object data to update struct.
290
     *
291
     * @param ContentObject       $object              Content object to map from
292
     * @param ContentUpdateStruct $contentUpdateStruct Content update struct to map to
293
     *
294
     * @throws \InvalidArgumentException
295
     */
296 7
    private function mapObjectToUpdateStruct(ContentObject $object, ContentUpdateStruct $contentUpdateStruct)
297
    {
298 7
        $this->assignStructFieldValues($object, $contentUpdateStruct);
299 7
    }
300
301
    /**
302
     * Copies content object data from a struct.
303
     *
304
     * @param ContentObject $object Content object to get values from
305
     * @param object        $struct Struct to assign values to
306
     */
307 10
    private function assignStructFieldValues(ContentObject $object, $struct)
308
    {
309 10
        foreach ($object->data as $key => $value) {
310 10
            if (is_array($value)) {
311 1
                $value = end($value);
312 1
            }
313
314 10
            $struct->setField($key, $value);
315 10
        }
316 10
    }
317
}
318