Completed
Pull Request — 1.0 (#53)
by Harald
06:50
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 11
    public function __construct(Repository $repository, LocationManager $locationManager)
71
    {
72 11
        $this->locationManager = $locationManager;
73 11
        $this->contentService = $repository->getContentService();
74 11
        $this->contentTypeService = $repository->getContentTypeService();
75 11
        $this->locationService = $repository->getLocationService();
76 11
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81 10
    public function setLogger(LoggerInterface $logger)
82
    {
83 10
        $this->logger = $logger;
84 10
    }
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 17
    public function find(ValueObject $object, $throwException = false)
98
    {
99
        try {
100 17
            if ($object->getProperty('remote_id')) {
101 17
                $content = $this->contentService->loadContentByRemoteId($object->getProperty('remote_id'));
102 14
            } elseif ($object->getProperty('id')) {
103
                $content = $this->contentService->loadContent($object->getProperty('id'));
104
            }
105 17
        } catch (NotFoundException $notFoundException) {
106 8
            $exception = $notFoundException;
107
        }
108
109 17
        if (!isset($content)) {
110 8
            if (isset($exception) && $throwException) {
111
                throw $exception;
112
            }
113
114 8
            return false;
115
        }
116
117 14
        $object = new ContentObject(array());
118 14
        $object->getMapper()->contentToObject($content);
119
120 14
        if ($content->contentInfo->published) {
121 14
            $locations = $this->locationService->loadLocations($content->contentInfo);
122 14
            $object->setParentLocations($locations);
123 14
        }
124
125 14
        $type = $this->contentTypeService->loadContentType($content->contentInfo->contentTypeId);
126 14
        $object->setProperty('content_type_identifier', $type->identifier);
127
128 14
        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 9
    public function create(ObjectInterface $object)
135
    {
136 9
        if (!$object instanceof ContentObject) {
137 1
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
138
        }
139
140 8
        $createStruct = $this->contentService->newContentCreateStruct(
141 8
            $this->contentTypeService->loadContentTypeByIdentifier($object->getProperty('content_type_identifier')),
142 8
            $object->getProperty('language')
143 8
        );
144
145 8
        $this->mapObjectToContentStruct($object, $createStruct);
146
147
        /** @var LocationObject[] $locationObjects */
148 8
        $locationObjects = $object->getProperty('parent_locations');
149 8
        $locationCreateStructs = [];
150 8
        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 8
        $content = $this->contentService->createContent($createStruct, $locationCreateStructs);
159 8
        $this->contentService->publishVersion($content->versionInfo);
160
161 8
        if ($this->logger) {
162 5
            $this->logger->info(sprintf('Published new version of %s', $object->getProperty('name')), array('ContentManager::create'));
163 5
        }
164
165 8
        $object->setProperty('id', $content->contentInfo->id);
166 8
        $object->setProperty('version_info', $content->versionInfo);
167 8
        $object->setProperty('content_info', $content->contentInfo);
168
169 8
        return $object;
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175 17
    public function update(ObjectInterface $object)
176 14
    {
177 14
        if (!$object instanceof ContentObject) {
178 1
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
179 17
        }
180
181 13
        $existingContent = $this->find($object);
182 13
        if (null === $object->getProperty('content_info')) {
183 11
            $object->setProperty('content_info', $existingContent->getProperty('content_info'));
184 11
        }
185
186 13
        $contentDraft = $this->contentService->createContentDraft($object->getProperty('content_info'));
187
188 13
        $contentUpdateStruct = $this->contentService->newContentUpdateStruct();
189 13
        $this->mapObjectToUpdateStruct($object, $contentUpdateStruct);
190
191 13
        $contentDraft = $this->contentService->updateContent($contentDraft->versionInfo, $contentUpdateStruct);
192 13
        $content = $this->contentService->publishVersion($contentDraft->versionInfo);
193
194 13
        if ($this->logger) {
195 5
            $this->logger->info(sprintf('Published new version of %s', $object->getProperty('name')), array('ContentManager::update'));
196 5
        }
197
198 13
        $object->setProperty('id', $content->contentInfo->id);
199 13
        $object->setProperty('version_info', $content->versionInfo);
200 13
        $object->setProperty('content_info', $content->contentInfo);
201
202
        // Add/Update/Delete parent locations
203 13
        $this->locationManager->syncronizeLocationsFromContentObject($object);
204
205 13
        return $object;
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211 18
    public function createOrUpdate(ObjectInterface $object)
212
    {
213 18
        if (!$object instanceof ContentObject) {
214 1
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
215
        }
216
217 17
        if (!$object->getProperty('content_id') && !$object->getProperty('remote_id')) {
218 1
            throw new MissingIdentificationPropertyException($object);
219
        }
220
221 16
        if ($this->find($object)) {
222 13
            return $this->update($object);
223
        } else {
224 8
            return $this->create($object);
225
        }
226
    }
227
228
    /**
229
     * {@inheritdoc}
230
     */
231 2
    public function remove(ObjectInterface $object)
232
    {
233 2
        if (!$object instanceof ContentObject) {
234 1
            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 14
    public function setMainLocation(ContentObject $object, Location $location)
257
    {
258 14
        $contentMetadataUpdateStruct = $this->contentService->newContentMetadataUpdateStruct();
259
260 3
        $contentMetadataUpdateStruct->mainLocationId = $location->id;
261
262 3
        $object->setProperty('main_location_id', $location->id);
263
264 3
        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 8
    private function mapObjectToContentStruct(ContentObject $object, ContentCreateStruct $createStruct)
276
    {
277 8
        $this->assignStructFieldValues($object, $createStruct);
278
279 8
        if ($object->getProperty('language')) {
280 8
            $createStruct->mainLanguageCode = $object->getProperty('language');
281 8
        }
282
283 8
        if ($object->getProperty('remote_id')) {
284 8
            $createStruct->remoteId = $object->getProperty('remote_id');
285 8
        }
286 8
    }
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 13
    private function mapObjectToUpdateStruct(ContentObject $object, ContentUpdateStruct $contentUpdateStruct)
297
    {
298 13
        $this->assignStructFieldValues($object, $contentUpdateStruct);
299 13
    }
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 16
    private function assignStructFieldValues(ContentObject $object, $struct)
308
    {
309 16
        foreach ($object->data as $key => $value) {
310 16
            if (is_array($value)) {
311 2
                $value = end($value);
312 2
            }
313
314 16
            $struct->setField($key, $value);
315 16
        }
316 16
    }
317
}
318