Completed
Pull Request — 1.0 (#53)
by Harald
06:31
created

ContentManager::find()   C

Complexity

Conditions 8
Paths 24

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 8

Importance

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