Module::resolve_model_tags()   F
last analyzed

Complexity

Conditions 19
Paths 800

Size

Total Lines 134
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 134
rs 2.3386
c 0
b 0
f 0
cc 19
eloc 47
nc 800
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the ICanBoogie package.
5
 *
6
 * (c) Olivier Laviale <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ICanBoogie;
13
14
use ICanBoogie\ActiveRecord\Connection;
15
use ICanBoogie\ActiveRecord\Model;
16
use ICanBoogie\ActiveRecord\ModelNotDefined;
17
use ICanBoogie\I18n;
18
use ICanBoogie\Module\Descriptor;
19
use ICanBoogie\Module\ModuleCollection;
20
21
/**
22
 * A module of the framework.
23
 *
24
 * @property-read array $descriptor The descriptor of the module.
25
 * @property-read string $flat_id Underscored identifier.
26
 * @property-read string $id The identifier of the module, defined by {@link Descriptor::ID}.
27
 * @property-read Model $model The primary model of the module.
28
 * @property-read Module $parent The parent module, defined by {@link Descriptor::INHERITS}.
29
 * @property-read string $path The path to the module, defined by {@link Descriptor::PATH}.
30
 * @property-read string $title The localized title of the module.
31
 * @property-read ModuleCollection $collection
32
 * @property-read Core|Module\CoreBindings|Binding\ActiveRecord\CoreBindings|Binding\I18n\CoreBindings $app
33
 */
34
class Module extends Prototyped
35
{
36
	/*
37
	 * PERMISSIONS:
38
	 *
39
	 * NONE: Well, you can't do anything
40
	 *
41
	 * ACCESS: You can access the module and view its records
42
	 *
43
	 * CREATE: You can create new records
44
	 *
45
	 * MAINTAIN: You can edit the records you created
46
	 *
47
	 * MANAGE: You can delete the records you created
48
	 *
49
	 * ADMINISTER: You have complete control over the module
50
	 *
51
	 */
52
	const PERMISSION_NONE = 0;
53
	const PERMISSION_ACCESS = 1;
54
	const PERMISSION_CREATE = 2;
55
	const PERMISSION_MAINTAIN = 3;
56
	const PERMISSION_MANAGE = 4;
57
	const PERMISSION_ADMINISTER = 5;
58
59
	/**
60
	 * Defines the name of the operation used to save the records of the module.
61
	 */
62
	const OPERATION_SAVE = 'save';
63
64
	/**
65
	 * Defines the name of the operation used to delete the records of the module.
66
	 */
67
	const OPERATION_DELETE = 'delete';
68
69
	/**
70
	 * Returns the identifier of the module as defined by its descriptor.
71
	 *
72
	 * This method is the getter for the {@link $id} magic property.
73
	 *
74
	 * @return string
75
	 */
76
	protected function get_id()
77
	{
78
		return $this->descriptor[Descriptor::ID];
79
	}
80
81
	/**
82
	 * Returns the path of the module as defined by its descriptor.
83
	 *
84
	 * This method is the getter for the {@link $path} magic property.
85
	 *
86
	 * @return string
87
	 */
88
	protected function get_path()
89
	{
90
		return $this->descriptor[Descriptor::PATH];
91
	}
92
93
	/**
94
	 * The descriptor of the module.
95
	 *
96
	 * @var array
97
	 */
98
	protected $descriptor;
99
100
	/**
101
	 * Returns the descriptor of the module.
102
	 *
103
	 * This method is the getter for the {@link $descriptor} magic property.
104
	 *
105
	 * @return array
106
	 */
107
	protected function get_descriptor()
108
	{
109
		return $this->descriptor;
110
	}
111
112
	/**
113
	 * Cache for loaded models.
114
	 *
115
	 * @var ActiveRecord\Model[]
116
	 */
117
	private $models = [];
118
119
	/**
120
	 * @var ModuleCollection
121
	 */
122
	private $collection;
123
124
	/**
125
	 * @return ModuleCollection
126
	 */
127
	protected function get_collection()
128
	{
129
		return $this->collection;
130
	}
131
132
	/**
133
	 * Constructor.
134
	 *
135
	 * Initializes the {@link $descriptor} property.
136
	 *
137
	 * @param ModuleCollection $collection
138
	 * @param array $descriptor
139
	 */
140
	public function __construct(ModuleCollection $collection, array $descriptor)
141
	{
142
		$this->collection = $collection;
143
		$this->descriptor = $descriptor;
144
	}
145
146
	/**
147
	 * Returns the identifier of the module.
148
	 *
149
	 * @return string
150
	 */
151
	public function __toString()
152
	{
153
		return $this->id;
154
	}
155
156
	/**
157
	 * Returns the _flat_ version of the module's identifier.
158
	 *
159
	 * This method is the getter for the {@link $flat_id} magic property.
160
	 *
161
	 * @return string
162
	 */
163
	protected function get_flat_id()
164
	{
165
		return strtr($this->id, [
166
167
			'.' => '_',
168
			'-' => '_'
169
170
		]);
171
	}
172
173
	/**
174
	 * Returns the primary model of the module.
175
	 *
176
	 * This is the getter for the {@link $model} magic property.
177
	 *
178
	 * @return ActiveRecord\Model
179
	 */
180
	protected function get_model()
181
	{
182
		return $this->model();
183
	}
184
185
	/**
186
	 * Returns the module title, translated to the current language.
187
	 *
188
	 * @return string
189
	 *
190
	 * @deprecated
191
	 */
192
	protected function get_title()
193
	{
194
		$default = isset($this->descriptor[Descriptor::TITLE]) ? $this->descriptor[Descriptor::TITLE] : 'Undefined';
195
196
		return $this->app->translate($this->flat_id, [], [ 'scope' => 'module_title', 'default' => $default ]);
0 ignored issues
show
Bug introduced by Olivier Laviale
The method translate does only exist in ICanBoogie\Binding\I18n\CoreBindings, but not in ICanBoogie\Binding\Activ...gie\Module\CoreBindings.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
197
	}
198
199
	/**
200
	 * Returns the parent module.
201
	 *
202
	 * @return Module|null
203
	 */
204
	protected function get_parent()
205
	{
206
		return $this->descriptor[Descriptor::INHERITS];
207
	}
208
209
	/**
210
	 * Checks if the module is installed.
211
	 *
212
	 * @param ErrorCollection $errors Error collection.
213
	 *
214
	 * @return mixed `true` if the module is installed, `false` if the module
215
	 * (or parts of) is not installed, `null` if the module has no installation.
216
	 */
217
	public function is_installed(ErrorCollection $errors)
218
	{
219
		if (empty($this->descriptor[Descriptor::MODELS]))
220
		{
221
			return null;
222
		}
223
224
		$rc = true;
225
226
		foreach ($this->descriptor[Descriptor::MODELS] as $name => $tags)
227
		{
228
			if (!$this->model($name)->is_installed())
229
			{
230
				$errors->add($this->id, "The model %name is not installed.", [
231
232
					'name' => $name
233
234
				]);
235
236
				$rc = false;
237
			}
238
		}
239
240
		return $rc;
241
	}
242
243
	/**
244
	 * Install the module.
245
	 *
246
	 * If the module has models they are installed.
247
	 *
248
	 * @param ErrorCollection $errors Error collection.
249
	 *
250
	 * @return boolean|null true if the module has successfully been installed, false if the
251
	 * module (or parts of the module) fails to install or null if the module has
252
	 * no installation process.
253
	 */
254
	public function install(ErrorCollection $errors)
255
	{
256
		if (empty($this->descriptor[Descriptor::MODELS]))
257
		{
258
			return null;
259
		}
260
261
		$rc = true;
262
263
		foreach ($this->descriptor[Descriptor::MODELS] as $name => $tags)
264
		{
265
			$model = $this->model($name);
266
267
			if ($model->is_installed())
268
			{
269
				continue;
270
			}
271
272
			try
273
			{
274
				$model->install();
275
			}
276
			catch (\Exception $e)
277
			{
278
				$errors->add($this->id, "Unable to install model %model: !message", [
279
280
					'model' => $name,
281
					'message' => $e->getMessage()
282
283
				]);
284
285
				$rc = false;
286
			}
287
		}
288
289
		return $rc;
290
	}
291
292
	/**
293
	 * Uninstall the module.
294
	 *
295
	 * Basically it uninstall the models installed by the module.
296
	 *
297
	 * @return boolean|null `true` if the module was successfully uninstalled. `false` if the module
298
	 * (or parts of the module) failed to uninstall. `null` if there is no uninstall process.
299
	 */
300
	public function uninstall()
301
	{
302
		if (empty($this->descriptor[Descriptor::MODELS]))
303
		{
304
			return null;
305
		}
306
307
		$rc = true;
308
309
		foreach ($this->descriptor[Descriptor::MODELS] as $name => $tags)
310
		{
311
			$model = $this->model($name);
312
313
			if (!$model->is_installed())
314
			{
315
				continue;
316
			}
317
318
			if (!$model->uninstall())
319
			{
320
				$rc = false;
321
			}
322
		}
323
324
		return $rc;
325
	}
326
327
	/**
328
	 * Get a model from the module.
329
	 *
330
	 * If the model has not been created yet, it is created on the fly.
331
	 *
332
	 * @param string $which The identifier of the model to get.
333
	 *
334
	 * @return Model The requested model.
335
	 *
336
	 * @throws ModelNotDefined when the model is not defined by the module.
337
	 * @throws \RuntimeException when the class of the model does not exists.
338
	 */
339
	public function model($which = 'primary')
340
	{
341
		if (empty($this->models[$which]))
342
		{
343
			if (empty($this->descriptor[Descriptor::MODELS][$which]))
344
			{
345
				throw new ModelNotDefined($which);
346
			}
347
348
			#
349
			# resolve model tags
350
			#
351
352
			$callback = "resolve_{$which}_model_tags";
353
354
			if (!method_exists($this, $callback))
355
			{
356
				$callback = 'resolve_model_tags';
357
			}
358
359
			$attributes = $this->$callback($this->descriptor[Descriptor::MODELS][$which], $which);
360
361
			#
362
			# COMPATIBILITY WITH 'inherit'
363
			#
364
365
			if ($attributes instanceof Model)
366
			{
367
				$this->models[$which] = $attributes;
368
369
				return $attributes;
370
			}
371
372
			#
373
			# create model
374
			#
375
376
			$class = $attributes[Model::CLASSNAME];
377
378
			if (!class_exists($class))
379
			{
380
				throw new \RuntimeException(\ICanBoogie\format("Unable to instantiate model %model, the class %class does not exists.", [
381
382
					'model' => "$this->id/$which",
383
					'class' => $class
384
385
				]));
386
			}
387
388
			$this->models[$which] = new $class($this->app->models, $attributes);
389
		}
390
391
		#
392
		# return cached model
393
		#
394
395
		return $this->models[$which];
396
	}
397
398
	/**
399
	 * Resolves model tags.
400
	 *
401
	 * @param array|string $tags
402
	 * @param string $which
403
	 *
404
	 * @return array
405
	 */
406
	protected function resolve_model_tags($tags, $which)
407
	{
408
		$app = $this->app;
409
410
		#
411
		# The model may use another model, in which case the model to use is defined using a
412
		# string e.g. 'contents' or 'terms/nodes'
413
		#
414
415
		if (is_string($tags))
416
		{
417
			$model_name = $tags;
418
419
			if ($model_name == 'inherit')
420
			{
421
				$class = get_parent_class($this);
422
423
				foreach ($app->modules->descriptors as $module_id => $descriptor)
424
				{
425
					if ($class != $descriptor['class'])
426
					{
427
						continue;
428
					}
429
430
					$model_name = $app->models[$module_id];
431
432
					break;
433
				}
434
			}
435
436
			$tags = [ Model::EXTENDING => $model_name ];
437
		}
438
439
		#
440
		# defaults
441
		#
442
443
		$id = $this->id;
444
445
		$tags += [
446
447
			Model::CONNECTION => 'primary',
448
			Model::ID => $which == 'primary' ? $id : $id . '/' . $which,
449
			Model::EXTENDING => null
450
451
		];
452
453
		if (empty($tags[Model::NAME]))
454
		{
455
			$tags[Model::NAME] = ModuleCollection::format_model_name($id, $which);
456
		}
457
458
		#
459
		# relations
460
		#
461
462
		if (isset($tags[Model::EXTENDING]))
463
		{
464
			$extends = &$tags[Model::EXTENDING];
465
466
			if (is_string($extends))
467
			{
468
				$extends = $this->app->models[$extends];
469
			}
470
471
			if (!$tags[Model::CLASSNAME])
472
			{
473
				$tags[Model::CLASSNAME] = get_class($extends);
474
			}
475
		}
476
477
		#
478
		#
479
		#
480
481
		if (isset($tags[Model::IMPLEMENTING]))
482
		{
483
			$implements =& $tags[Model::IMPLEMENTING];
484
485
			foreach ($implements as &$implement)
486
			{
487
				if (isset($implement['model']))
488
				{
489
					list($implement_id, $implement_which) = explode('/', $implement['model']) + [ 1 => 'primary' ];
490
491
					if ($id == $implement_id && $which == $implement_which)
492
					{
493
						throw new \RuntimeException(\ICanBoogie\format('Model %module/%model implements itself !', [
494
495
							'%module' => $id,
496
							'%model' => $which
497
498
						]));
499
					}
500
501
					$module = ($implement_id == $id) ? $this : \ICanBoogie\app()->modules[$implement_id];
502
503
					$implement['table'] = $module->model($implement_which);
504
				}
505
				else if (is_string($implement['table']))
506
				{
507
					throw new \RuntimeException(\ICanBoogie\format('Model %model of module %module implements a table: %table', [
508
509
						'%model' => $which,
510
						'%module' => $id,
511
						'%table' => $implement['table']
512
513
					]));
514
				}
515
			}
516
		}
517
518
		#
519
		# default class, if none was defined.
520
		#
521
522
		if (empty($tags[Model::CLASSNAME]))
523
		{
524
			$tags[Model::CLASSNAME] = 'ICanBoogie\ActiveRecord\Model';
525
		}
526
527
		#
528
		# connection
529
		#
530
531
		$connection = $tags[Model::CONNECTION];
532
533
		if (!($connection instanceof Connection))
534
		{
535
			$tags[Model::CONNECTION] = $this->app->connections[$connection];
536
		}
537
538
		return $tags;
539
	}
540
541
	/**
542
	 * Get a block.
543
	 *
544
	 * @param string $name The name of the block to get.
545
	 *
546
	 * @return mixed Depends on the implementation. Should return a string or an object
547
	 * implementing `__toString`.
548
	 *
549
	 * @throws \RuntimeException if the block is not defined.
550
	 */
551
	public function getBlock($name)
552
	{
553
		$args = func_get_args();
554
555
		array_shift($args);
556
557
		$callback = 'block_' . $name;
558
559
		if (!method_exists($this, $callback))
560
		{
561
			throw new \RuntimeException(\ICanBoogie\format('The %method method is missing from the %module module to create block %type.', [
562
563
				'%method' => $callback,
564
				'%module' => $this->id,
565
				'%type' => $name
566
567
			]));
568
		}
569
570
		return call_user_func_array([ $this, $callback ], $args);
571
	}
572
}
573