Completed
Push — master ( 838c87...d3b7f0 )
by Aimeos
02:57
created

Base   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 376
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 5

Importance

Changes 11
Bugs 2 Features 0
Metric Value
wmc 40
c 11
b 2
f 0
lcom 3
cbo 5
dl 0
loc 376
rs 8.2608

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A getChildItems() 0 4 1
A getContext() 0 4 1
B getDomains() 0 29 2
A getIds() 0 16 4
A getListItems() 0 4 1
A getPath() 0 4 1
A getRefItems() 0 21 3
A getTemplatePaths() 0 4 1
A getView() 0 4 1
A initCriteria() 0 8 1
A initCriteriaConditions() 0 12 2
A initCriteriaSlice() 0 7 3
A initCriteriaSortations() 0 19 4
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
	 * Returns the items with parent/child relationships
47
	 *
48
	 * @param array $items List of items implementing \Aimeos\MShop\Common\Item\Iface
49
	 * @param array $include List of resource types that should be fetched
50
	 * @return array List of items implementing \Aimeos\MShop\Common\Item\Iface
51
	 */
52
	protected function getChildItems( array $items, array $include )
53
	{
54
		return array();
55
	}
56
57
58
	/**
59
	 * Returns the context item object
60
	 *
61
	 * @return \Aimeos\MShop\Context\Item\Iface Context object
62
	 */
63
	protected function getContext()
64
	{
65
		return $this->context;
66
	}
67
68
69
	/**
70
	 * Returns the list of domains that are available as resources
71
	 *
72
	 * @param \Aimeos\MW\View\Iface $view View object with "resource" parameter
73
	 * @return array List of domain names
74
	 */
75
	protected function getDomains( \Aimeos\MW\View\Iface $view )
76
	{
77
		if( ( $domains = $view->param( 'resource' ) ) == '' )
78
		{
79
			/** admin/jsonadm/domains
80
			 * A list of domain names whose clients are available for the JSON API
81
			 *
82
			 * The HTTP OPTIONS method returns a list of resources known by the
83
			 * JSON API including their URLs. The list of available resources
84
			 * can be exteded dynamically be implementing a new Jsonadm client
85
			 * class handling request for this new domain.
86
			 *
87
			 * To add the new domain client to the list of resources returned
88
			 * by the HTTP OPTIONS method, you have to add its name in lower case
89
			 * to the existing configuration.
90
			 *
91
			 * @param array List of domain names
92
			 * @since 2016.01
93
			 * @category Developer
94
			 */
95
			$default = array(
96
				'attribute', 'catalog', 'coupon', 'customer', 'locale', 'media',
97
				'order', 'plugin', 'price', 'product', 'service', 'supplier', 'tag', 'text'
98
			);
99
			$domains = $this->getContext()->getConfig()->get( 'admin/jsonadm/domains', $default );
100
		}
101
102
		return (array) $domains;
103
	}
104
105
106
	/**
107
	 * Returns the IDs sent in the request body
108
	 *
109
	 * @param \stdClass $request Decoded request body
110
	 * @return array List of item IDs
111
	 */
112
	protected function getIds( $request )
113
	{
114
		$ids = array();
115
116
		if( isset( $request->data ) )
117
		{
118
			foreach( (array) $request->data as $entry )
119
			{
120
				if( isset( $entry->id ) ) {
121
					$ids[] = $entry->id;
122
				}
123
			}
124
		}
125
126
		return $ids;
127
	}
128
129
130
	/**
131
	 * Returns the list items for association relationships
132
	 *
133
	 * @param array $items List of items implementing \Aimeos\MShop\Common\Item\Iface
134
	 * @param array $include List of resource types that should be fetched
135
	 * @return array List of items implementing \Aimeos\MShop\Common\Item\Lists\Iface
136
	 */
137
	protected function getListItems( array $items, array $include )
138
	{
139
		return array();
140
	}
141
142
143
	/**
144
	 * Returns the path to the client
145
	 *
146
	 * @return string Client path, e.g. "product/property"
147
	 */
148
	protected function getPath()
149
	{
150
		return $this->path;
151
	}
152
153
154
	/**
155
	 * Returns the items associated via a lists table
156
	 *
157
	 * @param array $listItems List of items implementing \Aimeos\MShop\Common\Item\Lists\Iface
158
	 * @return array List of items implementing \Aimeos\MShop\Common\Item\Iface
159
	 */
160
	protected function getRefItems( array $listItems )
161
	{
162
		$list = $map = array();
163
		$context = $this->getContext();
164
165
		foreach( $listItems as $listItem ) {
166
			$map[$listItem->getDomain()][] = $listItem->getRefId();
167
		}
168
169
		foreach( $map as $domain => $ids )
170
		{
171
			$manager = \Aimeos\MShop\Factory::createManager( $context, $domain );
172
173
			$search = $manager->createSearch();
174
			$search->setConditions( $search->compare( '==', $domain . '.id', $ids ) );
175
176
			$list = array_merge( $list, $manager->searchItems( $search ) );
177
		}
178
179
		return $list;
180
	}
181
182
183
	/**
184
	 * Returns the paths to the template files
185
	 *
186
	 * @return array List of file system paths
187
	 */
188
	protected function getTemplatePaths()
189
	{
190
		return $this->templatePaths;
191
	}
192
193
194
	/**
195
	 * Returns the view object
196
	 *
197
	 * @return \Aimeos\MW\View\Iface View object
198
	 */
199
	protected function getView()
200
	{
201
		return $this->view;
202
	}
203
204
205
	/**
206
	 * Initializes the criteria object based on the given parameter
207
	 *
208
	 * @param \Aimeos\MW\Criteria\Iface $criteria Criteria object
209
	 * @param array $params List of criteria data with condition, sorting and paging
210
	 * @return \Aimeos\MW\Criteria\Iface Initialized criteria object
211
	 */
212
	protected function initCriteria( \Aimeos\MW\Criteria\Iface $criteria, array $params )
213
	{
214
		$this->initCriteriaConditions( $criteria, $params );
215
		$this->initCriteriaSortations( $criteria, $params );
216
		$this->initCriteriaSlice( $criteria, $params );
217
218
		return $criteria;
219
	}
220
221
222
	/**
223
	 * Initializes the criteria object with conditions based on the given parameter
224
	 *
225
	 * @param \Aimeos\MW\Criteria\Iface $criteria Criteria object
226
	 * @param array $params List of criteria data with condition, sorting and paging
227
	 */
228
	protected function initCriteriaConditions( \Aimeos\MW\Criteria\Iface $criteria, array $params )
229
	{
230
		if( !isset( $params['filter'] ) ) {
231
			return;
232
		}
233
234
		$existing = $criteria->getConditions();
235
		$criteria->setConditions( $criteria->toConditions( (array) $params['filter'] ) );
236
237
		$expr = array( $criteria->getConditions(), $existing );
238
		$criteria->setConditions( $criteria->combine( '&&', $expr ) );
239
	}
240
241
242
	/**
243
	 * Initializes the criteria object with the slice based on the given parameter.
244
	 *
245
	 * @param \Aimeos\MW\Criteria\Iface $criteria Criteria object
246
	 * @param array $params List of criteria data with condition, sorting and paging
247
	 */
248
	protected function initCriteriaSlice( \Aimeos\MW\Criteria\Iface $criteria, array $params )
249
	{
250
		$start = ( isset( $params['page']['offset'] ) ? (int) $params['page']['offset'] : 0 );
251
		$size = ( isset( $params['page']['limit'] ) ? (int) $params['page']['limit'] : 25 );
252
253
		$criteria->setSlice( $start, $size );
254
	}
255
256
257
	/**
258
	 * Initializes the criteria object with sortations based on the given parameter
259
	 *
260
	 * @param \Aimeos\MW\Criteria\Iface $criteria Criteria object
261
	 * @param array $params List of criteria data with condition, sorting and paging
262
	 */
263
	protected function initCriteriaSortations( \Aimeos\MW\Criteria\Iface $criteria, array $params )
264
	{
265
		if( !isset( $params['sort'] ) ) {
266
			return;
267
		}
268
269
		$sortation = array();
270
271
		foreach( explode( ',', $params['sort'] ) as $sort )
272
		{
273
			if( $sort[0] === '-' ) {
274
				$sortation[] = $criteria->sort( '-', substr( $sort, 1 ) );
275
			} else {
276
				$sortation[] = $criteria->sort( '+', $sort );
277
			}
278
		}
279
280
		$criteria->setSortations( $sortation );
281
	}
282
283
284
	/**
285
	 * Creates of updates several items at once
286
	 *
287
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager responsible for the items
288
	 * @param \stdClass $request Object with request body data
289
	 * @return array List of items
290
	 */
291
	protected function saveData( \Aimeos\MShop\Common\Manager\Iface $manager, \stdClass $request )
292
	{
293
		$data = array();
294
295
		if( isset( $request->data ) )
296
		{
297
			foreach( (array) $request->data as $entry ) {
298
				$data[] = $this->saveEntry( $manager, $entry );
299
			}
300
		}
301
302
		return $data;
303
	}
304
305
306
	/**
307
	 * Saves and returns the new or updated item
308
	 *
309
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager responsible for the items
310
	 * @param \stdClass $entry Object including "id" and "attributes" elements
311
	 * @return \Aimeos\MShop\Common\Item\Iface New or updated item
312
	 */
313
	protected function saveEntry( \Aimeos\MShop\Common\Manager\Iface $manager, \stdClass $entry )
314
	{
315
		if( isset( $entry->id ) ) {
316
			$item = $manager->getItem( $entry->id );
317
		} else {
318
			$item = $manager->createItem();
319
		}
320
321
		$item = $this->addItemData( $manager, $item, $entry, $item->getResourceType() );
322
		$manager->saveItem( $item );
323
324
		if( isset( $entry->relationships ) ) {
325
			$this->saveRelationships( $manager, $item, $entry->relationships );
326
		}
327
328
		return $manager->getItem( $item->getId() );
329
	}
330
331
332
	/**
333
	 * Saves the item references associated via the list
334
	 *
335
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager responsible for the items
336
	 * @param \Aimeos\MShop\Common\Item\Iface $item Domain item with an unique ID set
337
	 * @param \stdClass $relationships Object including the <domain>/data/attributes structure
338
	 */
339
	protected function saveRelationships( \Aimeos\MShop\Common\Manager\Iface $manager,
340
		\Aimeos\MShop\Common\Item\Iface $item, \stdClass $relationships )
341
	{
342
		$id = $item->getId();
343
		$listManager = $manager->getSubManager( 'lists' );
344
345
		foreach( (array) $relationships as $domain => $list )
346
		{
347
			if( isset( $list->data ) )
348
			{
349
				foreach( (array) $list->data as $data )
350
				{
351
					$listItem = $this->addItemData( $listManager, $listManager->createItem(), $data, $domain );
352
353
					if( isset( $data->id ) ) {
354
						$listItem->setRefId( $data->id );
355
					}
356
357
					$listItem->setParentId( $id );
358
					$listItem->setDomain( $domain );
359
360
					$listManager->saveItem( $listItem, false );
361
				}
362
			}
363
		}
364
	}
365
366
367
	/**
368
	 * Adds the data from the given object to the item
369
	 *
370
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object
371
	 * @param \Aimeos\MShop\Common\Item\Iface $item Item object to add the data to
372
	 * @param \stdClass $data Object with "attributes" property
373
	 * @param string $domain Domain of the type item
374
	 * @return \Aimeos\MShop\Common\Item\Iface Item including the data
375
	 */
376
	protected function addItemData(\Aimeos\MShop\Common\Manager\Iface $manager,
377
		\Aimeos\MShop\Common\Item\Iface $item, \stdClass $data, $domain )
378
	{
379
		if( isset( $data->attributes ) )
380
		{
381
			$attr = (array) $data->attributes;
382
			$key = str_replace( '/', '.', $item->getResourceType() );
383
384
			if( isset( $attr[$key.'.type'] ) )
385
			{
386
				$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...
387
				$attr[$key.'.typeid'] = $typeItem->getId();
388
			}
389
390
			$item->fromArray( $attr );
391
		}
392
393
		return $item;
394
	}
395
}
396