Completed
Push — EZP-31584 ( d4db81 )
by
unknown
18:34
created

LocationService::loadFirstAvailableLocation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
cc 1
nc 1
nop 2
rs 10
1
<?php
2
3
/**
4
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
5
 * @license For full copyright and license information view LICENSE file distributed with this source code.
6
 */
7
namespace eZ\Publish\Core\REST\Client;
8
9
use eZ\Publish\API\Repository\LocationService as APILocationService;
10
use eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct;
11
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
12
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
13
use eZ\Publish\API\Repository\Values\Content\Location;
14
use eZ\Publish\API\Repository\Values\Content\VersionInfo;
15
use eZ\Publish\Core\REST\Common\RequestParser;
16
use eZ\Publish\Core\REST\Common\Input\Dispatcher;
17
use eZ\Publish\Core\REST\Common\Output\Visitor;
18
use eZ\Publish\Core\REST\Common\Message;
19
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
20
21
/**
22
 * Location service, used for complex subtree operations.
23
 *
24
 * @example Examples/location.php
25
 */
26
class LocationService implements APILocationService, Sessionable
27
{
28
    /** @var \eZ\Publish\Core\REST\Client\HttpClient */
29
    private $client;
30
31
    /** @var \eZ\Publish\Core\REST\Common\Input\Dispatcher */
32
    private $inputDispatcher;
33
34
    /** @var \eZ\Publish\Core\REST\Common\Output\Visitor */
35
    private $outputVisitor;
36
37
    /** @var \eZ\Publish\Core\REST\Common\RequestParser */
38
    private $requestParser;
39
40
    /**
41
     * @param \eZ\Publish\Core\REST\Client\HttpClient $client
42
     * @param \eZ\Publish\Core\REST\Common\Input\Dispatcher $inputDispatcher
43
     * @param \eZ\Publish\Core\REST\Common\Output\Visitor $outputVisitor
44
     * @param \eZ\Publish\Core\REST\Common\RequestParser $requestParser
45
     */
46
    public function __construct(HttpClient $client, Dispatcher $inputDispatcher, Visitor $outputVisitor, RequestParser $requestParser)
47
    {
48
        $this->client = $client;
49
        $this->inputDispatcher = $inputDispatcher;
50
        $this->outputVisitor = $outputVisitor;
51
        $this->requestParser = $requestParser;
52
    }
53
54
    /**
55
     * Set session ID.
56
     *
57
     * Only for testing
58
     *
59
     * @param mixed $id
60
     *
61
     * @private
62
     */
63
    public function setSession($id)
64
    {
65
        if ($this->outputVisitor instanceof Sessionable) {
66
            $this->outputVisitor->setSession($id);
67
        }
68
    }
69
70
    /**
71
     * Instantiates a new location create class.
72
     *
73
     * @param mixed $parentLocationId the parent under which the new location should be created
74
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType|null $contentType
75
     *
76
     * @return \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct
77
     */
78 View Code Duplication
    public function newLocationCreateStruct($parentLocationId, ContentType $contentType = null)
79
    {
80
        $properties = [
81
            'parentLocationId' => $parentLocationId,
82
        ];
83
        if ($contentType) {
84
            $properties['sortField'] = $contentType->defaultSortField;
85
            $properties['sortOrder'] = $contentType->defaultSortOrder;
86
        }
87
88
        return new LocationCreateStruct($properties);
89
    }
90
91
    /**
92
     * Creates the new $location in the content repository for the given content.
93
     *
94
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to create this location
95
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException  if the content is already below the specified parent
96
     *                                        or the parent is a sub location of the location the content
97
     *                                        or if set the remoteId exists already
98
     *
99
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
100
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $locationCreateStruct
101
     *
102
     * @return \eZ\Publish\API\Repository\Values\Content\Location the newly created Location
103
     */
104
    public function createLocation(ContentInfo $contentInfo, LocationCreateStruct $locationCreateStruct)
105
    {
106
        $inputMessage = $this->outputVisitor->visit($locationCreateStruct);
107
        $inputMessage->headers['Accept'] = $this->outputVisitor->getMediaType('Location');
108
109
        $values = $this->requestParser->parse('object', $contentInfo->id);
110
        $result = $this->client->request(
111
            'POST',
112
            $this->requestParser->generate('objectLocations', ['object' => $values['object']]),
113
            $inputMessage
114
        );
115
116
        return $this->inputDispatcher->parse($result);
117
    }
118
119
    /**
120
     * {@inheritdoc).
121
     */
122
    public function loadLocation($locationId, array $prioritizedLanguages = null, bool $useAlwaysAvailable = null)
123
    {
124
        $response = $this->client->request(
125
            'GET',
126
            $locationId,
127
            new Message(
128
                ['Accept' => $this->outputVisitor->getMediaType('Location')]
129
            )
130
        );
131
132
        return $this->inputDispatcher->parse($response);
133
    }
134
135
    /**
136
     * {@inheritdoc).
137
     */
138 View Code Duplication
    public function loadLocationList(array $locationIds, array $prioritizedLanguages = null, bool $useAlwaysAvailable = null): iterable
139
    {
140
        // @todo Implement server part, ala: https://gist.github.com/andrerom/f2f328029ae7a9d78b363282b3ddf4a4
141
142
        $response = $this->client->request(
143
            'GET',
144
            $this->requestParser->generate('locationsByIds', ['locations' => $locationIds]),
145
            new Message(['Accept' => $this->outputVisitor->getMediaType('LocationList')])
146
        );
147
148
        return $this->inputDispatcher->parse($response);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->inputDispatcher->parse($response); (eZ\Publish\API\Repository\Values\ValueObject) is incompatible with the return type declared by the interface eZ\Publish\API\Repositor...rvice::loadLocationList of type eZ\Publish\API\Repositor...API\Repository\iterable.

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...
149
    }
150
151
    /**
152
     * {@inheritdoc).
153
     */
154 View Code Duplication
    public function loadLocationByRemoteId($remoteId, array $prioritizedLanguages = null, bool $useAlwaysAvailable = null)
155
    {
156
        $response = $this->client->request(
157
            'GET',
158
            $this->requestParser->generate('locationByRemote', ['location' => $remoteId]),
159
            new Message(
160
                ['Accept' => $this->outputVisitor->getMediaType('LocationList')]
161
            )
162
        );
163
164
        return reset($this->inputDispatcher->parse($response));
0 ignored issues
show
Bug introduced by
$this->inputDispatcher->parse($response) cannot be passed to reset() as the parameter $array expects a reference.
Loading history...
165
    }
166
167
    /**
168
     * Instantiates a new location update class.
169
     *
170
     * @return \eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct
171
     */
172
    public function newLocationUpdateStruct()
173
    {
174
        return new LocationUpdateStruct();
175
    }
176
177
    /**
178
     * Updates $location in the content repository.
179
     *
180
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to update this location
181
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException   if if set the remoteId exists already
182
     *
183
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
184
     * @param \eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct $locationUpdateStruct
185
     *
186
     * @return \eZ\Publish\API\Repository\Values\Content\Location the updated Location
187
     */
188
    public function updateLocation(Location $location, LocationUpdateStruct $locationUpdateStruct)
189
    {
190
        $inputMessage = $this->outputVisitor->visit($locationUpdateStruct);
191
        $inputMessage->headers['Accept'] = $this->outputVisitor->getMediaType('Location');
192
        $inputMessage->headers['X-HTTP-Method-Override'] = 'PATCH';
193
194
        $result = $this->client->request(
195
            'POST',
196
            $location->id,
197
            $inputMessage
198
        );
199
200
        return $this->inputDispatcher->parse($result);
201
    }
202
203
    /**
204
     * Loads the locations for the given content object.
205
     *
206
     * If a $rootLocation is given, only locations that belong to this location are returned.
207
     * The location list is also filtered by permissions on reading locations.
208
     *
209
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if there is no published version yet
210
     *
211
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
212
     * @param \eZ\Publish\API\Repository\Values\Content\Location $rootLocation
213
     * @param string[]|null $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
214
     *
215
     * @return \eZ\Publish\API\Repository\Values\Content\Location[]
216
     */
217
    public function loadLocations(ContentInfo $contentInfo, Location $rootLocation = null, array $prioritizedLanguages = null)
218
    {
219
        $values = $this->requestParser->parse('object', $contentInfo->id);
220
        $response = $this->client->request(
221
            'GET',
222
            $this->requestParser->generate('objectLocations', ['object' => $values['object']]),
223
            new Message(
224
                ['Accept' => $this->outputVisitor->getMediaType('LocationList')]
225
            )
226
        );
227
228
        return $this->inputDispatcher->parse($response);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->inputDispatcher->parse($response); (eZ\Publish\API\Repository\Values\ValueObject) is incompatible with the return type declared by the interface eZ\Publish\API\Repositor...nService::loadLocations of type eZ\Publish\API\Repositor...lues\Content\Location[].

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...
229
    }
230
231
    /**
232
     * Loads children which are readable by the current user of a location object sorted by sortField and sortOrder.
233
     *
234
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
235
     * @param int $offset the start offset for paging
236
     * @param int $limit the number of locations returned
237
     * @param string[]|null $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
238
     *
239
     * @return \eZ\Publish\API\Repository\Values\Content\LocationList
240
     */
241 View Code Duplication
    public function loadLocationChildren(Location $location, $offset = 0, $limit = 25, array $prioritizedLanguages = null)
242
    {
243
        $values = $this->requestParser->parse('location', $location->id);
244
        $response = $this->client->request(
245
            'GET',
246
            $this->requestParser->generate('locationChildren', ['location' => $values['location']]),
247
            new Message(
248
                ['Accept' => $this->outputVisitor->getMediaType('LocationList')]
249
            )
250
        );
251
252
        return $this->inputDispatcher->parse($response);
253
    }
254
255
    /**
256
     * Load parent Locations for Content Draft.
257
     *
258
     * @param \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo
259
     * @param string[]|null $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
260
     *
261
     * @return \eZ\Publish\API\Repository\Values\Content\Location[] List of parent Locations
262
     */
263
    public function loadParentLocationsForDraftContent(VersionInfo $versionInfo, array $prioritizedLanguages = null)
264
    {
265
        throw new \Exception('@todo: Implement.');
266
    }
267
268
    /**
269
     * Returns the number of children which are readable by the current user of a location object.
270
     *
271
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
272
     *
273
     * @return int
274
     */
275
    public function getLocationChildCount(Location $location)
276
    {
277
        throw new \Exception('@todo: Implement.');
278
    }
279
280
    /**
281
     * Swaps the contents hold by the $location1 and $location2.
282
     *
283
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to swap content
284
     *
285
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location1
286
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location2
287
     */
288
    public function swapLocation(Location $location1, Location $location2)
289
    {
290
        throw new \Exception('@todo: Implement.');
291
    }
292
293
    /**
294
     * Hides the $location and marks invisible all descendants of $location.
295
     *
296
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to hide this location
297
     *
298
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
299
     *
300
     * @return \eZ\Publish\API\Repository\Values\Content\Location $location, with updated hidden value
301
     */
302
    public function hideLocation(Location $location)
303
    {
304
        throw new \Exception('@todo: Implement.');
305
    }
306
307
    /**
308
     * Unhides the $location.
309
     *
310
     * This method and marks visible all descendants of $locations
311
     * until a hidden location is found.
312
     *
313
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to unhide this location
314
     *
315
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
316
     *
317
     * @return \eZ\Publish\API\Repository\Values\Content\Location $location, with updated hidden value
318
     */
319
    public function unhideLocation(Location $location)
320
    {
321
        throw new \Exception('@todo: Implement.');
322
    }
323
324
    /**
325
     * Deletes $location and all its descendants.
326
     *
327
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user is not allowed to delete this location or a descendant
328
     *
329
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
330
     */
331
    public function deleteLocation(Location $location)
332
    {
333
        throw new \Exception('@todo: Implement.');
334
    }
335
336
    /**
337
     * Copies the subtree starting from $subtree as a new subtree of $targetLocation.
338
     *
339
     * Only the items on which the user has read access are copied.
340
     *
341
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed copy the subtree to the given parent location
342
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException  if the target location is a sub location of the given location
343
     *
344
     * @param \eZ\Publish\API\Repository\Values\Content\Location $subtree - the subtree denoted by the location to copy
345
     * @param \eZ\Publish\API\Repository\Values\Content\Location $targetParentLocation - the target parent location for the copy operation
346
     *
347
     * @return \eZ\Publish\API\Repository\Values\Content\Location The newly created location of the copied subtree
348
     *
349
     * @todo enhancement - this method should return a result structure containing the new location and a list
350
     *       of locations which are not copied due to permission denials.
351
     */
352
    public function copySubtree(Location $subtree, Location $targetParentLocation)
353
    {
354
        throw new \Exception('@todo: Implement.');
355
    }
356
357
    /**
358
     * Moves the subtree to $newParentLocation.
359
     *
360
     * If a user has the permission to move the location to a target location
361
     * he can do it regardless of an existing descendant on which the user has no permission.
362
     *
363
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to move this location to the target
364
     *
365
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
366
     * @param \eZ\Publish\API\Repository\Values\Content\Location $newParentLocation
367
     */
368
    public function moveSubtree(Location $location, Location $newParentLocation)
369
    {
370
        throw new \Exception('@todo: Implement.');
371
    }
372
373
    public function getAllLocationsCount(): int
374
    {
375
        throw new \Exception('@todo: Implement.');
376
    }
377
378
    /**
379
     * @param int $limit
380
     * @param int $offset
381
     *
382
     * @return \eZ\Publish\API\Repository\Values\Content\Location[]
383
     *
384
     * @throws \Exception
385
     */
386
    public function loadAllLocations(int $offset = 0, int $limit = 25): array
387
    {
388
        throw new \Exception('@todo: Implement.');
389
    }
390
391
    /**
392
     * @throws \Exception
393
     */
394
    public function loadFirstAvailableLocation(ContentInfo $contentInfo, array $prioritizedLanguages = null): Location
395
    {
396
        throw new \Exception('@todo: Implement.');
397
    }
398
}
399