Completed
Pull Request — 1.0 (#53)
by Harald
05:27
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 8
    public function __construct(Repository $repository, LocationManager $locationManager)
71
    {
72 8
        $this->locationManager = $locationManager;
73 8
        $this->contentService = $repository->getContentService();
74 8
        $this->contentTypeService = $repository->getContentTypeService();
75 8
        $this->locationService = $repository->getLocationService();
76 8
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81 8
    public function setLogger(LoggerInterface $logger)
82
    {
83 8
        $this->logger = $logger;
84 8
    }
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 9
    public function find(ValueObject $object, $throwException = false)
98
    {
99
        try {
100 9
            if ($object->getProperty('remote_id')) {
101 9
                $content = $this->contentService->loadContentByRemoteId($object->getProperty('remote_id'));
102 6
            } elseif ($object->getProperty('id')) {
103
                $content = $this->contentService->loadContent($object->getProperty('id'));
104
            }
105 9
        } catch (NotFoundException $notFoundException) {
106 6
            $exception = $notFoundException;
107
        }
108
109 9
        if (!isset($content)) {
110 6
            if (isset($exception) && $throwException) {
111
                throw $exception;
112
            }
113
114 6
            return false;
115
        }
116
117 6
        $object = new ContentObject(array());
118 6
        $object->getMapper()->contentToObject($content);
119
120 6
        if ($content->contentInfo->published) {
121 6
            $locations = $this->locationService->loadLocations($content->contentInfo);
122 6
            $object->setParentLocations($locations);
123 6
        }
124
125 6
        $type = $this->contentTypeService->loadContentType($content->contentInfo->contentTypeId);
126 6
        $object->setProperty('content_type_identifier', $type->identifier);
127
128 6
        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 9
    public function update(ObjectInterface $object)
176 6
    {
177 6
        if (!$object instanceof ContentObject) {
178
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
179 9
        }
180
181 6
        $existingContent = $this->find($object);
182 6
        if (null === $object->getProperty('content_info')) {
183 5
            $object->setProperty('content_info', $existingContent->getProperty('content_info'));
184 5
        }
185
186 6
        $contentDraft = $this->contentService->createContentDraft($object->getProperty('content_info'));
187
188 6
        $contentUpdateStruct = $this->contentService->newContentUpdateStruct();
189 6
        $this->mapObjectToUpdateStruct($object, $contentUpdateStruct);
190
191 6
        $contentDraft = $this->contentService->updateContent($contentDraft->versionInfo, $contentUpdateStruct);
192 6
        $content = $this->contentService->publishVersion($contentDraft->versionInfo);
193
194 6
        if ($this->logger) {
195 4
            $this->logger->info(sprintf('Published new version of %s', $object->getProperty('name')), array('ContentManager::update'));
196 4
        }
197
198 6
        $object->setProperty('id', $content->contentInfo->id);
199 6
        $object->setProperty('version_info', $content->versionInfo);
200 6
        $object->setProperty('content_info', $content->contentInfo);
201
202
        // Add/Update/Delete parent locations
203 6
        $this->locationManager->syncronizeLocationsFromContentObject($object);
204
205 6
        return $object;
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211 10
    public function createOrUpdate(ObjectInterface $object)
212
    {
213 10
        if (!$object instanceof ContentObject) {
214
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
215
        }
216
217 10
        if (!$object->getProperty('content_id') && !$object->getProperty('remote_id')) {
218 1
            throw new MissingIdentificationPropertyException($object);
219
        }
220
221 9
        if ($this->find($object)) {
222 6
            return $this->update($object);
223 1
        } else {
224 6
            return $this->create($object);
225
        }
226
    }
227
228
    /**
229
     * {@inheritdoc}
230
     */
231
    public function remove(ObjectInterface $object)
232
    {
233
        if (!$object instanceof ContentObject) {
234
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
235
        }
236
237
        $object = $this->find($object);
238
239
        if ($object instanceof ContentObject && $object->getProperty('content_info')) {
240
            $this->contentService->deleteContent($object->getProperty('content_info'));
241
242
            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 6
    public function setMainLocation(ContentObject $object, Location $location)
257
    {
258 6
        $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 6
    private function mapObjectToUpdateStruct(ContentObject $object, ContentUpdateStruct $contentUpdateStruct)
297
    {
298 6
        $this->assignStructFieldValues($object, $contentUpdateStruct);
299 6
    }
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 9
    private function assignStructFieldValues(ContentObject $object, $struct)
308
    {
309 9
        foreach ($object->data as $key => $value) {
310 9
            if (is_array($value)) {
311 1
                $value = end($value);
312 1
            }
313
314 9
            $struct->setField($key, $value);
315 9
        }
316 9
    }
317
}
318