Completed
Push — master ( 265fe5...2a3526 )
by Aimeos
03:01
created

Base   C

Complexity

Total Complexity 79

Size/Duplication

Total Lines 941
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 10
Bugs 2 Features 0
Metric Value
wmc 79
c 10
b 2
f 0
lcom 1
cbo 9
dl 0
loc 941
rs 5

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B delete() 0 64 4
A get() 0 56 3
B patch() 0 64 4
B post() 0 64 4
B put() 0 39 1
B options() 0 71 4
B deleteItems() 0 22 5
B getItem() 0 25 3
A getView() 0 4 1
A initCriteria() 0 8 1
A initCriteriaConditions() 0 11 2
A initCriteriaSlice() 0 7 3
A initCriteriaSortations() 0 19 4
B getDomains() 0 29 2
A getChildItems() 0 4 1
A getListItems() 0 4 1
A getRefItems() 0 21 3
A getIds() 0 16 4
A getContext() 0 4 1
A getTemplatePaths() 0 4 1
A getPath() 0 4 1
B patchItems() 0 31 5
B postItems() 0 32 6
A saveData() 0 13 3
A saveEntry() 0 17 3
B saveRelationships() 0 26 5
A addItemData() 0 19 3

How to fix   Complexity   

Complex Class

Complex classes like Base often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Base, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2016
6
 * @package Admin
7
 * @subpackage JsonAdm
8
 */
9
10
11
namespace Aimeos\Admin\JsonAdm;
12
13
14
/**
15
 * JSON API common client
16
 *
17
 * @package Admin
18
 * @subpackage JsonAdm
19
 */
20
class Base
21
{
22
	private $view;
23
	private $context;
24
	private $templatePaths;
25
	private $path;
26
27
28
	/**
29
	 * Initializes the client
30
	 *
31
	 * @param \Aimeos\MShop\Context\Item\Iface $context MShop context object
32
	 * @param \Aimeos\MW\View\Iface $view View object
33
	 * @param array $templatePaths List of file system paths where the templates are stored
34
	 * @param string $path Name of the client separated by slashes, e.g "product/stock"
35
	 */
36
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MW\View\Iface $view, array $templatePaths, $path )
37
	{
38
		$this->view = $view;
39
		$this->context = $context;
40
		$this->templatePaths = $templatePaths;
41
		$this->path = $path;
42
	}
43
44
45
	/**
46
	 * Deletes the resource or the resource list
47
	 *
48
	 * @param string $body Request body
49
	 * @param array &$header Variable which contains the HTTP headers and the new ones afterwards
50
	 * @param integer &$status Variable which contains the HTTP status afterwards
51
	 * @return string Content for response body
52
	 */
53
	public function delete( $body, array &$header, &$status )
54
	{
55
		$header = array( 'Content-Type' => 'application/vnd.api+json; supported-ext="bulk"' );
56
		$view = $this->getView();
57
58
		try
59
		{
60
			$view = $this->deleteItems( $view, $body );
61
			$status = 200;
62
		}
63
		catch( \Aimeos\Admin\JsonAdm\Exception $e )
64
		{
65
			$status = $e->getCode();
66
			$view->errors = array( array(
67
				'title' => $this->getContext()->getI18n()->dt( 'admin/jsonadm', $e->getMessage() ),
68
				'detail' => $e->getTraceAsString(),
69
			) );
70
		}
71
		catch( \Aimeos\MShop\Exception $e )
72
		{
73
			$status = 404;
74
			$view->errors = array( array(
75
				'title' => $this->getContext()->getI18n()->dt( 'mshop', $e->getMessage() ),
76
				'detail' => $e->getTraceAsString(),
77
			) );
78
		}
79
		catch( \Exception $e )
80
		{
81
			$status = 500;
82
			$view->errors = array( array(
83
				'title' => $e->getMessage(),
84
				'detail' => $e->getTraceAsString(),
85
			) );
86
		}
87
88
		/** admin/jsonadm/standard/template-delete
89
		 * Relative path to the JSON API template for DELETE requests
90
		 *
91
		 * The template file contains the code and processing instructions
92
		 * to generate the result shown in the JSON API body. The
93
		 * configuration string is the path to the template file relative
94
		 * to the templates directory (usually in admin/jsonadm/templates).
95
		 *
96
		 * You can overwrite the template file configuration in extensions and
97
		 * provide alternative templates. These alternative templates should be
98
		 * named like the default one but with the string "standard" replaced by
99
		 * an unique name. You may use the name of your project for this. If
100
		 * you've implemented an alternative client class as well, "standard"
101
		 * should be replaced by the name of the new class.
102
		 *
103
		 * @param string Relative path to the template creating the body for the DELETE method of the JSON API
104
		 * @since 2015.12
105
		 * @category Developer
106
		 * @see admin/jsonadm/standard/template-get
107
		 * @see admin/jsonadm/standard/template-patch
108
		 * @see admin/jsonadm/standard/template-post
109
		 * @see admin/jsonadm/standard/template-put
110
		 * @see admin/jsonadm/standard/template-options
111
		 */
112
		$tplconf = 'admin/jsonadm/standard/template-delete';
113
		$default = 'delete-default.php';
114
115
		return $view->render( $view->config( $tplconf, $default ) );
116
	}
117
118
119
	/**
120
	 * Returns the requested resource or the resource list
121
	 *
122
	 * @param string $body Request body
123
	 * @param array &$header Variable which contains the HTTP headers and the new ones afterwards
124
	 * @param integer &$status Variable which contains the HTTP status afterwards
125
	 * @return string Content for response body
126
	 */
127
	public function get( $body, array &$header, &$status )
128
	{
129
		$header = array( 'Content-Type' => 'application/vnd.api+json; supported-ext="bulk"' );
130
		$view = $this->getView();
131
132
		try
133
		{
134
			$view = $this->getItem( $view );
135
			$status = 200;
136
		}
137
		catch( \Aimeos\MShop\Exception $e )
138
		{
139
			$status = 404;
140
			$view->errors = array( array(
141
				'title' => $this->getContext()->getI18n()->dt( 'mshop', $e->getMessage() ),
142
				'detail' => $e->getTraceAsString(),
143
			) );
144
		}
145
		catch( \Exception $e )
146
		{
147
			$status = 500;
148
			$view->errors = array( array(
149
				'title' => $e->getMessage(),
150
				'detail' => $e->getTraceAsString(),
151
			) );
152
		}
153
154
		/** admin/jsonadm/standard/template-get
155
		 * Relative path to the JSON API template for GET requests
156
		 *
157
		 * The template file contains the code and processing instructions
158
		 * to generate the result shown in the JSON API body. The
159
		 * configuration string is the path to the template file relative
160
		 * to the templates directory (usually in admin/jsonadm/templates).
161
		 *
162
		 * You can overwrite the template file configuration in extensions and
163
		 * provide alternative templates. These alternative templates should be
164
		 * named like the default one but with the string "standard" replaced by
165
		 * an unique name. You may use the name of your project for this. If
166
		 * you've implemented an alternative client class as well, "standard"
167
		 * should be replaced by the name of the new class.
168
		 *
169
		 * @param string Relative path to the template creating the body for the GET method of the JSON API
170
		 * @since 2015.12
171
		 * @category Developer
172
		 * @see admin/jsonadm/standard/template-delete
173
		 * @see admin/jsonadm/standard/template-patch
174
		 * @see admin/jsonadm/standard/template-post
175
		 * @see admin/jsonadm/standard/template-put
176
		 * @see admin/jsonadm/standard/template-options
177
		 */
178
		$tplconf = 'admin/jsonadm/standard/template-get';
179
		$default = 'get-default.php';
180
181
		return $view->render( $view->config( $tplconf, $default ) );
182
	}
183
184
185
	/**
186
	 * Updates the resource or the resource list partitially
187
	 *
188
	 * @param string $body Request body
189
	 * @param array &$header Variable which contains the HTTP headers and the new ones afterwards
190
	 * @param integer &$status Variable which contains the HTTP status afterwards
191
	 * @return string Content for response body
192
	 */
193
	public function patch( $body, array &$header, &$status )
194
	{
195
		$header = array( 'Content-Type' => 'application/vnd.api+json; supported-ext="bulk"' );
196
		$view = $this->getView();
197
198
		try
199
		{
200
			$view = $this->patchItems( $view, $body, $header );
201
			$status = 200;
202
		}
203
		catch( \Aimeos\Admin\JsonAdm\Exception $e )
204
		{
205
			$status = $e->getCode();
206
			$view->errors = array( array(
207
				'title' => $this->getContext()->getI18n()->dt( 'admin/jsonadm', $e->getMessage() ),
208
				'detail' => $e->getTraceAsString(),
209
			) );
210
		}
211
		catch( \Aimeos\MShop\Exception $e )
212
		{
213
			$status = 404;
214
			$view->errors = array( array(
215
				'title' => $this->getContext()->getI18n()->dt( 'mshop', $e->getMessage() ),
216
				'detail' => $e->getTraceAsString(),
217
			) );
218
		}
219
		catch( \Exception $e )
220
		{
221
			$status = 500;
222
			$view->errors = array( array(
223
				'title' => $e->getMessage(),
224
				'detail' => $e->getTraceAsString(),
225
			) );
226
		}
227
228
		/** admin/jsonadm/standard/template-patch
229
		 * Relative path to the JSON API template for PATCH requests
230
		 *
231
		 * The template file contains the code and processing instructions
232
		 * to generate the result shown in the JSON API body. The
233
		 * configuration string is the path to the template file relative
234
		 * to the templates directory (usually in admin/jsonadm/templates).
235
		 *
236
		 * You can overwrite the template file configuration in extensions and
237
		 * provide alternative templates. These alternative templates should be
238
		 * named like the default one but with the string "standard" replaced by
239
		 * an unique name. You may use the name of your project for this. If
240
		 * you've implemented an alternative client class as well, "standard"
241
		 * should be replaced by the name of the new class.
242
		 *
243
		 * @param string Relative path to the template creating the body for the PATCH method of the JSON API
244
		 * @since 2015.12
245
		 * @category Developer
246
		 * @see admin/jsonadm/standard/template-get
247
		 * @see admin/jsonadm/standard/template-post
248
		 * @see admin/jsonadm/standard/template-delete
249
		 * @see admin/jsonadm/standard/template-put
250
		 * @see admin/jsonadm/standard/template-options
251
		 */
252
		$tplconf = 'admin/jsonadm/standard/template-patch';
253
		$default = 'patch-default.php';
254
255
		return $view->render( $view->config( $tplconf, $default ) );
256
	}
257
258
259
	/**
260
	 * Creates or updates the resource or the resource list
261
	 *
262
	 * @param string $body Request body
263
	 * @param array &$header Variable which contains the HTTP headers and the new ones afterwards
264
	 * @param integer &$status Variable which contains the HTTP status afterwards
265
	 * @return string Content for response body
266
	 */
267
	public function post( $body, array &$header, &$status )
268
	{
269
		$header = array( 'Content-Type' => 'application/vnd.api+json; supported-ext="bulk"' );
270
		$view = $this->getView();
271
272
		try
273
		{
274
			$view = $this->postItems( $view, $body, $header );
275
			$status = 201;
276
		}
277
		catch( \Aimeos\Admin\JsonAdm\Exception $e )
278
		{
279
			$status = $e->getCode();
280
			$view->errors = array( array(
281
				'title' => $this->getContext()->getI18n()->dt( 'admin/jsonadm', $e->getMessage() ),
282
				'detail' => $e->getTraceAsString(),
283
			) );
284
		}
285
		catch( \Aimeos\MShop\Exception $e )
286
		{
287
			$status = 404;
288
			$view->errors = array( array(
289
				'title' => $this->getContext()->getI18n()->dt( 'mshop', $e->getMessage() ),
290
				'detail' => $e->getTraceAsString(),
291
			) );
292
		}
293
		catch( \Exception $e )
294
		{
295
			$status = 500;
296
			$view->errors = array( array(
297
				'title' => $e->getMessage(),
298
				'detail' => $e->getTraceAsString(),
299
			) );
300
		}
301
302
		/** admin/jsonadm/standard/template-post
303
		 * Relative path to the JSON API template for POST requests
304
		 *
305
		 * The template file contains the code and processing instructions
306
		 * to generate the result shown in the JSON API body. The
307
		 * configuration string is the path to the template file relative
308
		 * to the templates directory (usually in admin/jsonadm/templates).
309
		 *
310
		 * You can overwrite the template file configuration in extensions and
311
		 * provide alternative templates. These alternative templates should be
312
		 * named like the default one but with the string "standard" replaced by
313
		 * an unique name. You may use the name of your project for this. If
314
		 * you've implemented an alternative client class as well, "standard"
315
		 * should be replaced by the name of the new class.
316
		 *
317
		 * @param string Relative path to the template creating the body for the POST method of the JSON API
318
		 * @since 2015.12
319
		 * @category Developer
320
		 * @see admin/jsonadm/standard/template-get
321
		 * @see admin/jsonadm/standard/template-patch
322
		 * @see admin/jsonadm/standard/template-delete
323
		 * @see admin/jsonadm/standard/template-put
324
		 * @see admin/jsonadm/standard/template-options
325
		 */
326
		$tplconf = 'admin/jsonadm/standard/template-post';
327
		$default = 'post-default.php';
328
329
		return $view->render( $view->config( $tplconf, $default ) );
330
	}
331
332
333
	/**
334
	 * Creates or updates the resource or the resource list
335
	 *
336
	 * @param string $body Request body
337
	 * @param array &$header Variable which contains the HTTP headers and the new ones afterwards
338
	 * @param integer &$status Variable which contains the HTTP status afterwards
339
	 * @return string Content for response body
340
	 */
341
	public function put( $body, array &$header, &$status )
342
	{
343
		$status = 501;
344
		$header = array( 'Content-Type' => 'application/vnd.api+json; supported-ext="bulk"' );
345
		$view = $this->getView();
346
347
		$view->errors = array( array(
348
			'title' => $this->getContext()->getI18n()->dt( 'admin/jsonadm', 'Not implemented, use PATCH instead' ),
349
		) );
350
351
		/** admin/jsonadm/standard/template-put
352
		 * Relative path to the JSON API template for PUT requests
353
		 *
354
		 * The template file contains the code and processing instructions
355
		 * to generate the result shown in the JSON API body. The
356
		 * configuration string is the path to the template file relative
357
		 * to the templates directory (usually in admin/jsonadm/templates).
358
		 *
359
		 * You can overwrite the template file configuration in extensions and
360
		 * provide alternative templates. These alternative templates should be
361
		 * named like the default one but with the string "standard" replaced by
362
		 * an unique name. You may use the name of your project for this. If
363
		 * you've implemented an alternative client class as well, "standard"
364
		 * should be replaced by the name of the new class.
365
		 *
366
		 * @param string Relative path to the template creating the body for the PUT method of the JSON API
367
		 * @since 2015.12
368
		 * @category Developer
369
		 * @see admin/jsonadm/standard/template-delete
370
		 * @see admin/jsonadm/standard/template-patch
371
		 * @see admin/jsonadm/standard/template-post
372
		 * @see admin/jsonadm/standard/template-get
373
		 * @see admin/jsonadm/standard/template-options
374
		 */
375
		$tplconf = 'admin/jsonadm/standard/template-put';
376
		$default = 'put-default.php';
377
378
		return $view->render( $view->config( $tplconf, $default ) );
379
	}
380
381
382
	/**
383
	 * Returns the available REST verbs and the available resources
384
	 *
385
	 * @param string $body Request body
386
	 * @param array &$header Variable which contains the HTTP headers and the new ones afterwards
387
	 * @param integer &$status Variable which contains the HTTP status afterwards
388
	 * @return string Content for response body
389
	 */
390
	public function options( $body, array &$header, &$status )
391
	{
392
		$context = $this->getContext();
393
		$view = $this->getView();
394
395
		try
396
		{
397
			$resources = $attributes = array();
398
399
			foreach( $this->getDomains( $view ) as $domain )
400
			{
401
				$manager = \Aimeos\MShop\Factory::createManager( $context, $domain );
402
				$resources = array_merge( $resources, $manager->getResourceType( true ) );
403
				$attributes = array_merge( $attributes, $manager->getSearchAttributes( true ) );
404
			}
405
406
			$view->resources = $resources;
407
			$view->attributes = $attributes;
408
409
			$header = array(
410
				'Content-Type' => 'application/vnd.api+json; supported-ext="bulk"',
411
				'Allow' => 'DELETE,GET,POST,OPTIONS'
412
			);
413
			$status = 200;
414
		}
415
		catch( \Aimeos\MShop\Exception $e )
416
		{
417
			$status = 404;
418
			$view->errors = array( array(
419
				'title' => $context->getI18n()->dt( 'mshop', $e->getMessage() ),
420
				'detail' => $e->getTraceAsString(),
421
			) );
422
		}
423
		catch( \Exception $e )
424
		{
425
			$status = 500;
426
			$view->errors = array( array(
427
				'title' => $e->getMessage(),
428
				'detail' => $e->getTraceAsString(),
429
			) );
430
		}
431
432
		/** admin/jsonadm/standard/template-options
433
		 * Relative path to the JSON API template for OPTIONS requests
434
		 *
435
		 * The template file contains the code and processing instructions
436
		 * to generate the result shown in the JSON API body. The
437
		 * configuration string is the path to the template file relative
438
		 * to the templates directory (usually in admin/jsonadm/templates).
439
		 *
440
		 * You can overwrite the template file configuration in extensions and
441
		 * provide alternative templates. These alternative templates should be
442
		 * named like the default one but with the string "standard" replaced by
443
		 * an unique name. You may use the name of your project for this. If
444
		 * you've implemented an alternative client class as well, "standard"
445
		 * should be replaced by the name of the new class.
446
		 *
447
		 * @param string Relative path to the template creating the body for the OPTIONS method of the JSON API
448
		 * @since 2015.12
449
		 * @category Developer
450
		 * @see admin/jsonadm/standard/template-delete
451
		 * @see admin/jsonadm/standard/template-patch
452
		 * @see admin/jsonadm/standard/template-post
453
		 * @see admin/jsonadm/standard/template-get
454
		 * @see admin/jsonadm/standard/template-put
455
		 */
456
		$tplconf = 'admin/jsonadm/standard/template-options';
457
		$default = 'options-default.php';
458
459
		return $view->render( $view->config( $tplconf, $default ) );
460
	}
461
462
463
	/**
464
	 * Deletes one or more items
465
	 *
466
	 * @param \Aimeos\MW\View\Iface $view View instance with "param" view helper
467
	 * @param string $body Request body
468
	 * @return \Aimeos\MW\View\Iface $view View object that will contain the "total" property afterwards
469
	 * @throws \Aimeos\Admin\JsonAdm\Exception If the request body is invalid
470
	 */
471
	protected function deleteItems( \Aimeos\MW\View\Iface $view, $body )
472
	{
473
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), $this->getPath() );
474
475
		if( ( $id = $view->param( 'id' ) ) == null )
476
		{
477
			if( ( $request = json_decode( $body ) ) === null || !isset( $request->data ) || !is_array( $request->data ) ) {
478
				throw new \Aimeos\Admin\JsonAdm\Exception( sprintf( 'Invalid JSON in body' ), 400 );
479
			}
480
481
			$ids = $this->getIds( $request );
482
			$manager->deleteItems( $ids );
483
			$view->total = count( $ids );
484
		}
485
		else
486
		{
487
			$manager->deleteItem( $id );
488
			$view->total = 1;
489
		}
490
491
		return $view;
492
	}
493
494
495
	/**
496
	 * Retrieves the item or items and adds the data to the view
497
	 *
498
	 * @param \Aimeos\MW\View\Iface $view View instance
499
	 * @return \Aimeos\MW\View\Iface View instance with additional data assigned
500
	 */
501
	protected function getItem( \Aimeos\MW\View\Iface $view )
502
	{
503
		$total = 1;
504
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), $this->getPath() );
505
		$include = ( ( $include = $view->param( 'include' ) ) !== null ? explode( ',', $include ) : array() );
506
507
		if( ( $id = $view->param( 'id' ) ) == null )
508
		{
509
			$search = $this->initCriteria( $manager->createSearch(), $view->param() );
510
			$view->data = $manager->searchItems( $search, array(), $total );
511
			$view->childItems = $this->getChildItems( $view->data, $include );
512
			$view->listItems = $this->getListItems( $view->data, $include );
513
		}
514
		else
515
		{
516
			$view->data = $manager->getItem( $id, array() );
517
			$view->childItems = $this->getChildItems( array( $id => $view->data ), $include );
518
			$view->listItems = $this->getListItems( array( $id => $view->data ), $include );
519
		}
520
521
		$view->refItems = $this->getRefItems( $view->listItems );
522
		$view->total = $total;
523
524
		return $view;
525
	}
526
527
	/**
528
	 * Returns the view object
529
	 *
530
	 * @return \Aimeos\MW\View\Iface View object
531
	 */
532
	protected function getView()
533
	{
534
		return $this->view;
535
	}
536
537
538
	/**
539
	 * Initializes the criteria object based on the given parameter
540
	 *
541
	 * @param \Aimeos\MW\Criteria\Iface $criteria Criteria object
542
	 * @param array $params List of criteria data with condition, sorting and paging
543
	 * @return \Aimeos\MW\Criteria\Iface Initialized criteria object
544
	 */
545
	protected function initCriteria( \Aimeos\MW\Criteria\Iface $criteria, array $params )
546
	{
547
		$this->initCriteriaConditions( $criteria, $params );
548
		$this->initCriteriaSortations( $criteria, $params );
549
		$this->initCriteriaSlice( $criteria, $params );
550
551
		return $criteria;
552
	}
553
554
555
	/**
556
	 * Initializes the criteria object with conditions based on the given parameter
557
	 *
558
	 * @param \Aimeos\MW\Criteria\Iface $criteria Criteria object
559
	 * @param array $params List of criteria data with condition, sorting and paging
560
	 */
561
	private function initCriteriaConditions( \Aimeos\MW\Criteria\Iface $criteria, array $params )
562
	{
563
		if( isset( $params['filter'] ) )
564
		{
565
			$existing = $criteria->getConditions();
566
			$criteria->setConditions( $criteria->toConditions( (array) $params['filter'] ) );
567
568
			$expr = array( $criteria->getConditions(), $existing );
569
			$criteria->setConditions( $criteria->combine( '&&', $expr ) );
570
		}
571
	}
572
573
574
	/**
575
	 * Initializes the criteria object with the slice based on the given parameter.
576
	 *
577
	 * @param \Aimeos\MW\Criteria\Iface $criteria Criteria object
578
	 * @param array $params List of criteria data with condition, sorting and paging
579
	 */
580
	private function initCriteriaSlice( \Aimeos\MW\Criteria\Iface $criteria, array $params )
581
	{
582
		$start = ( isset( $params['page']['offset'] ) ? (int) $params['page']['offset'] : 0 );
583
		$size = ( isset( $params['page']['limit'] ) ? (int) $params['page']['limit'] : 25 );
584
585
		$criteria->setSlice( $start, $size );
586
	}
587
588
589
	/**
590
	 * Initializes the criteria object with sortations based on the given parameter
591
	 *
592
	 * @param \Aimeos\MW\Criteria\Iface $criteria Criteria object
593
	 * @param array $params List of criteria data with condition, sorting and paging
594
	 */
595
	private function initCriteriaSortations( \Aimeos\MW\Criteria\Iface $criteria, array $params )
596
	{
597
		if( !isset( $params['sort'] ) ) {
598
			return;
599
		}
600
601
		$sortation = array();
602
603
		foreach( explode( ',', $params['sort'] ) as $sort )
604
		{
605
			if( $sort[0] === '-' ) {
606
				$sortation[] = $criteria->sort( '-', substr( $sort, 1 ) );
607
			} else {
608
				$sortation[] = $criteria->sort( '+', $sort );
609
			}
610
		}
611
612
		$criteria->setSortations( $sortation );
613
	}
614
615
616
	/**
617
	 * Returns the list of domains that are available as resources
618
	 *
619
	 * @param \Aimeos\MW\View\Iface $view View object with "resource" parameter
620
	 * @return array List of domain names
621
	 */
622
	protected function getDomains( \Aimeos\MW\View\Iface $view )
623
	{
624
		if( ( $domains = $view->param( 'resource' ) ) == '' )
625
		{
626
			/** admin/jsonadm/domains
627
			 * A list of domain names whose clients are available for the JSON API
628
			 *
629
			 * The HTTP OPTIONS method returns a list of resources known by the
630
			 * JSON API including their URLs. The list of available resources
631
			 * can be exteded dynamically be implementing a new Jsonadm client
632
			 * class handling request for this new domain.
633
			 *
634
			 * To add the new domain client to the list of resources returned
635
			 * by the HTTP OPTIONS method, you have to add its name in lower case
636
			 * to the existing configuration.
637
			 *
638
			 * @param array List of domain names
639
			 * @since 2016.01
640
			 * @category Developer
641
			 */
642
			$default = array(
643
				'attribute', 'catalog', 'coupon', 'customer', 'locale', 'media',
644
				'order', 'plugin', 'price', 'product', 'service', 'supplier', 'tag', 'text'
645
			);
646
			$domains = $this->getContext()->getConfig()->get( 'admin/jsonadm/domains', $default );
647
		}
648
649
		return (array) $domains;
650
	}
651
652
653
	/**
654
	 * Returns the items with parent/child relationships
655
	 *
656
	 * @param array $items List of items implementing \Aimeos\MShop\Common\Item\Iface
657
	 * @param array $include List of resource types that should be fetched
658
	 * @return array List of items implementing \Aimeos\MShop\Common\Item\Iface
659
	 */
660
	protected function getChildItems( array $items, array $include )
661
	{
662
		return array();
663
	}
664
665
666
	/**
667
	 * Returns the list items for association relationships
668
	 *
669
	 * @param array $items List of items implementing \Aimeos\MShop\Common\Item\Iface
670
	 * @param array $include List of resource types that should be fetched
671
	 * @return array List of items implementing \Aimeos\MShop\Common\Item\Lists\Iface
672
	 */
673
	protected function getListItems( array $items, array $include )
674
	{
675
		return array();
676
	}
677
678
679
	/**
680
	 * Returns the items associated via a lists table
681
	 *
682
	 * @param array $listItems List of items implementing \Aimeos\MShop\Common\Item\Lists\Iface
683
	 * @return array List of items implementing \Aimeos\MShop\Common\Item\Iface
684
	 */
685
	protected function getRefItems( array $listItems )
686
	{
687
		$list = $map = array();
688
		$context = $this->getContext();
689
690
		foreach( $listItems as $listItem ) {
691
			$map[$listItem->getDomain()][] = $listItem->getRefId();
692
		}
693
694
		foreach( $map as $domain => $ids )
695
		{
696
			$manager = \Aimeos\MShop\Factory::createManager( $context, $domain );
697
698
			$search = $manager->createSearch();
699
			$search->setConditions( $search->compare( '==', $domain . '.id', $ids ) );
700
701
			$list = array_merge( $list, $manager->searchItems( $search ) );
702
		}
703
704
		return $list;
705
	}
706
707
708
	/**
709
	 * Returns the IDs sent in the request body
710
	 *
711
	 * @param \stdClass $request Decoded request body
712
	 * @return array List of item IDs
713
	 */
714
	protected function getIds( $request )
715
	{
716
		$ids = array();
717
718
		if( isset( $request->data ) )
719
		{
720
			foreach( (array) $request->data as $entry )
721
			{
722
				if( isset( $entry->id ) ) {
723
					$ids[] = $entry->id;
724
				}
725
			}
726
		}
727
728
		return $ids;
729
	}
730
731
732
	/**
733
	 * Returns the context item object
734
	 *
735
	 * @return \Aimeos\MShop\Context\Item\Iface Context object
736
	 */
737
	protected function getContext()
738
	{
739
		return $this->context;
740
	}
741
742
743
	/**
744
	 * Returns the paths to the template files
745
	 *
746
	 * @return array List of file system paths
747
	 */
748
	protected function getTemplatePaths()
749
	{
750
		return $this->templatePaths;
751
	}
752
753
754
	/**
755
	 * Returns the path to the client
756
	 *
757
	 * @return string Client path, e.g. "product/property"
758
	 */
759
	protected function getPath()
760
	{
761
		return $this->path;
762
	}
763
764
765
	/**
766
	 * Saves new attributes for one or more items
767
	 *
768
	 * @param \Aimeos\MW\View\Iface $view View that will contain the "data" and "total" properties afterwards
769
	 * @param string $body Request body
770
	 * @param array &$header Associative list of HTTP headers as value/result parameter
771
	 * @throws \Aimeos\Admin\JsonAdm\Exception If "id" parameter isn't available or the body is invalid
772
	 * @return \Aimeos\MW\View\Iface Updated view instance
773
	 */
774
	protected function patchItems( \Aimeos\MW\View\Iface $view, $body, array &$header )
775
	{
776
		if( ( $request = json_decode( $body ) ) === null || !isset( $request->data ) ) {
777
			throw new \Aimeos\Admin\JsonAdm\Exception( sprintf( 'Invalid JSON in body' ), 400 );
778
		}
779
780
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), $this->getPath() );
781
782
		if( is_array( $request->data ) )
783
		{
784
			$data = $this->saveData( $manager, $request );
785
786
			$view->data = $data;
787
			$view->total = count( $data );
788
			$header['Content-Type'] = 'application/vnd.api+json; ext="bulk"; supported-ext="bulk"';
789
		}
790
		elseif( ( $id = $view->param( 'id' ) ) != null )
791
		{
792
			$request->data->id = $id;
793
			$data = $this->saveEntry( $manager, $request->data );
794
795
			$view->data = $data;
796
			$view->total = 1;
797
		}
798
		else
799
		{
800
			throw new \Aimeos\Admin\JsonAdm\Exception( sprintf( 'No ID given' ), 400 );
801
		}
802
803
		return $view;
804
	}
805
806
807
	/**
808
	 * Creates one or more new items
809
	 *
810
	 * @param \Aimeos\MW\View\Iface $view View that will contain the "data" and "total" properties afterwards
811
	 * @param string $body Request body
812
	 * @param array &$header Associative list of HTTP headers as value/result parameter
813
	 * @return \Aimeos\MW\View\Iface Updated view instance
814
	 */
815
	protected function postItems( \Aimeos\MW\View\Iface $view, $body, array &$header )
816
	{
817
		if( ( $request = json_decode( $body ) ) === null || !isset( $request->data ) ) {
818
			throw new \Aimeos\Admin\JsonAdm\Exception( sprintf( 'Invalid JSON in body' ), 400 );
819
		}
820
821
		if( isset( $request->data->id ) || $view->param( 'id' ) != null ) {
822
			throw new \Aimeos\Admin\JsonAdm\Exception( sprintf( 'Client generated IDs are not supported' ), 403 );
823
		}
824
825
826
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), $this->getPath() );
827
828
		if( is_array( $request->data ) )
829
		{
830
			$data = $this->saveData( $manager, $request );
831
832
			$view->data = $data;
833
			$view->total = count( $data );
834
			$header['Content-Type'] = 'application/vnd.api+json; ext="bulk"; supported-ext="bulk"';
835
		}
836
		else
837
		{
838
			$request->data->id = null;
839
			$data = $this->saveEntry( $manager, $request->data );
840
841
			$view->data = $data;
842
			$view->total = 1;
843
		}
844
845
		return $view;
846
	}
847
848
849
	/**
850
	 * Creates of updates several items at once
851
	 *
852
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager responsible for the items
853
	 * @param \stdClass $request Object with request body data
854
	 * @return array List of items
855
	 */
856
	protected function saveData( \Aimeos\MShop\Common\Manager\Iface $manager, \stdClass $request )
857
	{
858
		$data = array();
859
860
		if( isset( $request->data ) )
861
		{
862
			foreach( (array) $request->data as $entry ) {
863
				$data[] = $this->saveEntry( $manager, $entry );
864
			}
865
		}
866
867
		return $data;
868
	}
869
870
871
	/**
872
	 * Saves and returns the new or updated item
873
	 *
874
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager responsible for the items
875
	 * @param \stdClass $entry Object including "id" and "attributes" elements
876
	 * @return \Aimeos\MShop\Common\Item\Iface New or updated item
877
	 */
878
	protected function saveEntry( \Aimeos\MShop\Common\Manager\Iface $manager, \stdClass $entry )
879
	{
880
		if( isset( $entry->id ) ) {
881
			$item = $manager->getItem( $entry->id );
882
		} else {
883
			$item = $manager->createItem();
884
		}
885
886
		$item = $this->addItemData( $manager, $item, $entry, $item->getResourceType() );
887
		$manager->saveItem( $item );
888
889
		if( isset( $entry->relationships ) ) {
890
			$this->saveRelationships( $manager, $item, $entry->relationships );
891
		}
892
893
		return $manager->getItem( $item->getId() );
894
	}
895
896
897
	/**
898
	 * Saves the item references associated via the list
899
	 *
900
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager responsible for the items
901
	 * @param \Aimeos\MShop\Common\Item\Iface $item Domain item with an unique ID set
902
	 * @param \stdClass $relationships Object including the <domain>/data/attributes structure
903
	 */
904
	protected function saveRelationships( \Aimeos\MShop\Common\Manager\Iface $manager,
905
		\Aimeos\MShop\Common\Item\Iface $item, \stdClass $relationships )
906
	{
907
		$id = $item->getId();
908
		$listManager = $manager->getSubManager( 'lists' );
909
910
		foreach( (array) $relationships as $domain => $list )
911
		{
912
			if( isset( $list->data ) )
913
			{
914
				foreach( (array) $list->data as $data )
915
				{
916
					$listItem = $this->addItemData( $listManager, $listManager->createItem(), $data, $domain );
917
918
					if( isset( $data->id ) ) {
919
						$listItem->setRefId( $data->id );
920
					}
921
922
					$listItem->setParentId( $id );
923
					$listItem->setDomain( $domain );
924
925
					$listManager->saveItem( $listItem, false );
926
				}
927
			}
928
		}
929
	}
930
931
932
	/**
933
	 * Adds the data from the given object to the item
934
	 *
935
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object
936
	 * @param \Aimeos\MShop\Common\Item\Iface $item Item object to add the data to
937
	 * @param \stdClass $data Object with "attributes" property
938
	 * @param string $domain Domain of the type item
939
	 * @return \Aimeos\MShop\Common\Item\Iface Item including the data
940
	 */
941
	protected function addItemData(\Aimeos\MShop\Common\Manager\Iface $manager,
942
		\Aimeos\MShop\Common\Item\Iface $item, \stdClass $data, $domain )
943
	{
944
		if( isset( $data->attributes ) )
945
		{
946
			$attr = (array) $data->attributes;
947
			$key = str_replace( '/', '.', $item->getResourceType() );
948
949
			if( isset( $attr[$key.'.type'] ) )
950
			{
951
				$typeItem = $manager->getSubManager( 'type' )->findItem( $attr[$key.'.type'], array(), $domain );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Common\Manager\Iface as the method findItem() does only exist in the following implementations of said interface: Aimeos\MShop\Attribute\Manager\Lists\Type\Standard, Aimeos\MShop\Attribute\Manager\Standard, Aimeos\MShop\Attribute\Manager\Type\Standard, Aimeos\MShop\Catalog\Manager\Decorator\Base, Aimeos\MShop\Catalog\Manager\Decorator\Sitecheck, Aimeos\MShop\Catalog\Manager\Lists\Type\Standard, Aimeos\MShop\Catalog\Manager\Standard, Aimeos\MShop\Common\Manager\Decorator\Base, Aimeos\MShop\Common\Manager\Decorator\Changelog, Aimeos\MShop\Common\Manager\Decorator\Sitecheck, Aimeos\MShop\Common\Manager\Type\Base, Aimeos\MShop\Coupon\Manager\Code\Standard, Aimeos\MShop\Customer\Manager\Base, Aimeos\MShop\Customer\Manager\Group\Standard, Aimeos\MShop\Customer\Manager\Lists\Type\Standard, Aimeos\MShop\Customer\Manager\Standard, Aimeos\MShop\Locale\Manager\Site\Standard, Aimeos\MShop\Media\Manager\Lists\Type\Standard, Aimeos\MShop\Media\Manager\Type\Standard, Aimeos\MShop\Plugin\Manager\Type\Standard, Aimeos\MShop\Price\Manager\Lists\Type\Standard, Aimeos\MShop\Price\Manager\Type\Standard, Aimeos\MShop\Product\Manager\Lists\Type\Standard, Aimeos\MShop\Product\Man...\Property\Type\Standard, Aimeos\MShop\Product\Manager\Standard, Aimeos\MShop\Product\Man...tock\Warehouse\Standard, Aimeos\MShop\Product\Manager\Type\Standard, Aimeos\MShop\Service\Manager\Lists\Type\Standard, Aimeos\MShop\Service\Manager\Standard, Aimeos\MShop\Service\Manager\Type\Standard, Aimeos\MShop\Supplier\Manager\Lists\Type\Standard, Aimeos\MShop\Supplier\Manager\Standard, Aimeos\MShop\Tag\Manager\Type\Standard, Aimeos\MShop\Text\Manager\Lists\Type\Standard, Aimeos\MShop\Text\Manager\Type\Standard.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
952
				$attr[$key.'.typeid'] = $typeItem->getId();
953
			}
954
955
			$item->fromArray( $attr );
956
		}
957
958
		return $item;
959
	}
960
}
961