Completed
Pull Request — 1.0 (#53)
by Harald
08:20 queued 01:22
created

ContentManager::find()   C

Complexity

Conditions 7
Paths 16

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7

Importance

Changes 7
Bugs 0 Features 1
Metric Value
c 7
b 0
f 1
dl 0
loc 31
ccs 19
cts 19
cp 1
rs 6.7272
cc 7
eloc 18
nc 16
nop 2
crap 7
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 30
    public function setLogger(LoggerInterface $logger)
82
    {
83 30
        $this->logger = $logger;
84 30
    }
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 21
    public function find(ValueObject $object, $throwException = false)
98
    {
99
        try {
100 21
            if ($object->getProperty('remote_id')) {
101 21
                $content = $this->contentService->loadContentByRemoteId($object->getProperty('remote_id'));
102 16
            }
103 21
        } catch (NotFoundException $notFoundException) {
104 10
            $exception = $notFoundException;
105
        }
106
107 21
        if (!isset($content)) {
108 10
            if (isset($exception) && $throwException) {
109 1
                throw $exception;
110
            }
111
112 9
            return false;
113
        }
114
115 16
        $object = new ContentObject(array());
116 16
        $object->getMapper()->contentToObject($content);
117
118 16
        if ($content->contentInfo->published) {
119 16
            $locations = $this->locationService->loadLocations($content->contentInfo);
120 16
            $object->setParentLocations($locations);
121 16
        }
122
123 16
        $type = $this->contentTypeService->loadContentType($content->contentInfo->contentTypeId);
124 16
        $object->setProperty('content_type_identifier', $type->identifier);
125
126 16
        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...
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132 9
    public function create(ObjectInterface $object)
133
    {
134 9
        if (!$object instanceof ContentObject) {
135 1
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
136
        }
137
138 8
        $createStruct = $this->contentService->newContentCreateStruct(
139 8
            $this->contentTypeService->loadContentTypeByIdentifier($object->getProperty('content_type_identifier')),
140 8
            $object->getProperty('language')
141 8
        );
142
143 8
        $this->mapObjectToContentStruct($object, $createStruct);
144
145
        /** @var LocationObject[] $locationObjects */
146 8
        $locationObjects = $object->getProperty('parent_locations');
147 8
        $locationCreateStructs = [];
148 8
        if (is_array($locationObjects) && count($locationObjects) > 0) {
149 2
            foreach ($locationObjects as $locationObject) {
150 2
                $locationCreateStruct = $this->locationService->newLocationCreateStruct($locationObject->data['parent_location_id']);
151 2
                $locationObject->getMapper()->getNewLocationCreateStruct($locationCreateStruct);
152 2
                $locationCreateStructs[] = $locationCreateStruct;
153 2
            }
154 2
        }
155
156 8
        $content = $this->contentService->createContent($createStruct, $locationCreateStructs);
157 8
        $this->contentService->publishVersion($content->versionInfo);
158
159 8
        if ($this->logger) {
160 8
            $this->logger->info(sprintf('Published new version of %s', $object->getProperty('name')), array('ContentManager::create'));
161 8
        }
162
163 8
        $object->setProperty('id', $content->contentInfo->id);
164 8
        $object->setProperty('version_info', $content->versionInfo);
165 8
        $object->setProperty('content_info', $content->contentInfo);
166
167 8
        return $object;
168
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173 19
    public function update(ObjectInterface $object)
174
    {
175 16
        if (!$object instanceof ContentObject) {
176 17
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
177
        }
178
179 19
        $existingContent = $this->find($object);
180 15
        if (null === $object->getProperty('content_info')) {
181 13
            $object->setProperty('content_info', $existingContent->getProperty('content_info'));
182 13
        }
183
184 15
        $contentDraft = $this->contentService->createContentDraft($object->getProperty('content_info'));
185
186 15
        $contentUpdateStruct = $this->contentService->newContentUpdateStruct();
187 15
        $this->mapObjectToUpdateStruct($object, $contentUpdateStruct);
188
189 15
        $contentDraft = $this->contentService->updateContent($contentDraft->versionInfo, $contentUpdateStruct);
190 15
        $content = $this->contentService->publishVersion($contentDraft->versionInfo);
191
192 15
        if ($this->logger) {
193 15
            $this->logger->info(sprintf('Published new version of %s', $object->getProperty('name')), array('ContentManager::update'));
194 15
        }
195
196 15
        $object->setProperty('id', $content->contentInfo->id);
197 15
        $object->setProperty('version_info', $content->versionInfo);
198 15
        $object->setProperty('content_info', $content->contentInfo);
199
200
        // Add/Update/Delete parent locations
201 15
        $this->locationManager->syncronizeLocationsFromContentObject($object);
202
203 15
        return $object;
204
    }
205
206
    /**
207
     * {@inheritdoc}
208
     */
209 20
    public function createOrUpdate(ObjectInterface $object)
210
    {
211 20
        if (!$object instanceof ContentObject) {
212 1
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
213
        }
214
215 19
        if (!$object->getProperty('content_id') && !$object->getProperty('remote_id')) {
216 3
            throw new MissingIdentificationPropertyException($object);
217
        }
218
219 18
        if ($this->find($object)) {
220 15
            return $this->update($object);
221
        } else {
222 8
            return $this->create($object);
223
        }
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229 3
    public function remove(ObjectInterface $object)
230
    {
231 3
        if (!$object instanceof ContentObject) {
232 1
            throw new UnsupportedObjectOperationException(ContentObject::class, get_class($object));
233
        }
234
235 2
        $object = $this->find($object);
236
237 2
        if ($object instanceof ContentObject && $object->getProperty('content_info')) {
238 1
            $this->contentService->deleteContent($object->getProperty('content_info'));
239
240 1
            return true;
241
        }
242
243 1
        return false;
244
    }
245
246
    /**
247
     * Assigns a main location ID for a content object.
248
     *
249
     * @param ContentObject $object   Content object
250
     * @param Location      $location Location
251
     *
252
     * @return Content
253
     */
254 16
    public function setMainLocation(ContentObject $object, Location $location)
255
    {
256 3
        $contentMetadataUpdateStruct = $this->contentService->newContentMetadataUpdateStruct();
257
258 16
        $contentMetadataUpdateStruct->mainLocationId = $location->id;
259
260 3
        $object->setProperty('main_location_id', $location->id);
261
262 3
        return $this->contentService->updateContentMetadata($object->getProperty('content_info'), $contentMetadataUpdateStruct);
263
    }
264
265
    /**
266
     * Maps object data to create struct.
267
     *
268
     * @param ContentObject       $object       Content object to map from
269
     * @param ContentCreateStruct $createStruct Content create struct to map to
270
     *
271
     * @throws \InvalidArgumentException
272
     */
273 8
    private function mapObjectToContentStruct(ContentObject $object, ContentCreateStruct $createStruct)
274
    {
275 8
        $this->assignStructFieldValues($object, $createStruct);
276
277 8
        if ($object->getProperty('language')) {
278 8
            $createStruct->mainLanguageCode = $object->getProperty('language');
279 8
        }
280
281 8
        if ($object->getProperty('remote_id')) {
282 8
            $createStruct->remoteId = $object->getProperty('remote_id');
283 8
        }
284 8
    }
285
286
    /**
287
     * Maps object data to update struct.
288
     *
289
     * @param ContentObject       $object              Content object to map from
290
     * @param ContentUpdateStruct $contentUpdateStruct Content update struct to map to
291
     *
292
     * @throws \InvalidArgumentException
293
     */
294 15
    private function mapObjectToUpdateStruct(ContentObject $object, ContentUpdateStruct $contentUpdateStruct)
295
    {
296 15
        $this->assignStructFieldValues($object, $contentUpdateStruct);
297 15
    }
298
299
    /**
300
     * Copies content object data from a struct.
301
     *
302
     * @param ContentObject $object Content object to get values from
303
     * @param object        $struct Struct to assign values to
304
     */
305 18
    private function assignStructFieldValues(ContentObject $object, $struct)
306
    {
307 18
        foreach ($object->data as $key => $value) {
308 18
            if (is_array($value)) {
309 2
                $value = end($value);
310 2
            }
311
312 18
            $struct->setField($key, $value);
313 18
        }
314 18
    }
315
}
316