Completed
Push — master ( 83a35a...e6e2aa )
by Thomas
07:45
created

ModelService   C

Complexity

Total Complexity 51

Size/Duplication

Total Lines 376
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 70.8%

Importance

Changes 13
Bugs 1 Features 1
Metric Value
wmc 51
c 13
b 1
f 1
lcom 1
cbo 17
dl 0
loc 376
rs 5.2703
ccs 80
cts 113
cp 0.708

17 Methods

Rating   Name   Duplication   Size   Complexity  
A isCoreSchema() 0 3 1
A hasSchema() 0 4 3
A getDatabase() 0 9 2
A getTableName() 0 8 2
A getModelNames() 0 9 2
B getModels() 0 16 5
B getSchema() 0 28 6
A getModel() 0 7 1
B getPackageModelNames() 0 23 6
A hasModel() 0 3 1
B getModelName() 0 13 5
A getModelNameByAction() 0 8 2
A getFullModelObjectName() 0 8 1
A getOperationByAction() 0 8 2
A isModelAction() 0 4 1
A isCrudAction() 0 5 1
C getRelationships() 0 65 10

How to fix   Complexity   

Complex Class

Complex classes like ModelService 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 ModelService, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace keeko\tools\services;
3
4
use keeko\framework\schema\ActionSchema;
5
use keeko\framework\schema\PackageSchema;
6
use phootwork\collection\ArrayList;
7
use phootwork\lang\Text;
8
use Propel\Generator\Model\Database;
9
use Propel\Generator\Model\Table;
10
use Propel\Generator\Util\QuickBuilder;
11
use phootwork\collection\Set;
12
13
class ModelService extends AbstractService {
14
15
	private $models = null;
16
	private $schema = null;
17
	
18
	/** @var Database */
19
	private $database = null;
20
21
	/**
22
	 * Returns the propel schema. The three locations, where the schema is looked up in:
23
	 *
24
	 * 1. --schema option (if available)
25
	 * 2. res/database/schema.xml
26
	 * 3. <keeko/core>/res/database/schema.xml
27
	 *
28
	 * @throws \RuntimeException
29
	 * @return string the path to the schema
30
	 */
31 12
	public function getSchema() {
32 12
		$input = $this->io->getInput();
33 12
		if ($this->schema === null) {
34 12
			$workDir = $this->service->getProject()->getRootPath();
35 12
			$schema = null;
36
			$schemas = [
37 12
				$input->hasOption('schema') ? $input->getOption('schema') : '',
38 12
				$workDir . '/database/schema.xml',
39
				$workDir . '/res/database/schema.xml',
40 12
				$workDir . '/vendor/keeko/core/database/schema.xml',
41 12
				$workDir . '/vendor/keeko/core/res/database/schema.xml'
42 12
			];
43 12
			foreach ($schemas as $path) {
44 12
				if (file_exists($path)) {
45
					$schema = $path;
46 12
					break;
47 12
				}
48
			}
49 12
			$this->schema = $schema;
50
				
51
			if ($schema === null) {
52
				$locations = implode(', ', $schemas);
53 12
				throw new \RuntimeException(sprintf('Can\'t find schema in these locations: %s', $locations));
54
			}
55 12
		}
56
57
		return $this->schema;
58 3
	}
59 3
	
60
	public function isCoreSchema() {
61
		return strpos($this->getSchema(), 'core') !== false;
62
	}
63
	
64
	public function hasSchema() {
65
		$vendorName = $this->packageService->getPackage()->getVendor();
66
		return $this->getSchema() !== null && ($this->isCoreSchema() ? $vendorName == 'keeko' : true);
67
	}
68
	
69
	/**
70
	 * Returns the propel database
71
	 *
72 12
	 * @return Database
73 12
	 */
74 12
	public function getDatabase() {
75 12
		if ($this->database === null) {
76 12
			$builder = new QuickBuilder();
77 12
			$builder->setSchema(file_get_contents($this->getSchema()));
78
			$this->database = $builder->getDatabase();
79 12
		}
80
	
81
		return $this->database;
82
	}
83
	
84
	/**
85
	 * Returns the tableName for a given name
86
	 *
87
	 * @param String $name tableName or modelName
88 11
	 * @return String tableName
89 11
	 */
90 11
	public function getTableName($name) {
91 11
		$db = $this->getDatabase();
92 11
		if (!Text::create($name)->startsWith($db->getTablePrefix())) {
93
			$name = $db->getTablePrefix() . $name;
94 11
		}
95
	
96
		return $name;
97
	}
98
	
99
	/**
100
	 * Returns all model names
101
	 *
102
	 * @return String[] an array of modelName
103
	 */
104
	public function getModelNames() {
105
		$names = [];
106
		$database = $this->getDatabase();
107
		foreach ($database->getTables() as $table) {
108
			$names[] = $table->getOriginCommonName();
109
		}
110
	
111
		return $names;
112
	}
113
	
114
	/**
115
	 * Returns the propel models from the database, where table namespace matches package namespace
116
	 *
117 1
	 * @return ArrayList<Table>
118 1
	 */
119 1
	public function getModels() {
120 1
		if ($this->models === null) {
121
			$namespace = $this->packageService->getNamespace() . '\\model';
122 1
			$propel = $this->getDatabase();
123
	
124 1
			$this->models = new ArrayList();
125 1
	
126 1
			foreach ($propel->getTables() as $table) {
127 1
				if (!$table->isCrossRef() && $table->getNamespace() == $namespace) {
128 1
					$this->models->add($table);
129 1
				}
130
			}
131 1
		}
132
	
133
		return $this->models;
134
	}
135
136
	/**
137
	 * Returns the model for the given name
138
	 *
139
	 * @param String $name modelName or tableName
140 7
	 * @return Table
141 7
	 */
142 7
	public function getModel($name) {
143
		$tableName = $this->getTableName($name);
144
		$db = $this->getDatabase();
145
		$table = $db->getTable($tableName);
146
	
147 7
		return $table;
148
	}
149 7
	
150
	/**
151
	 * Returns the model names for a given package
152
	 * 
153
	 * @param PackageSchema $package a package to search models for, if omitted global package is used
154
	 * @return array array with string of model names
155
	 */
156
	public function getPackageModelNames(PackageSchema $package = null) {
157
		if ($package === null) {
158 8
			$package = $this->packageService->getPackage();
159 8
		}
160
		
161
		$models = [];
162
		// if this is a core-module, find the related model
163
		if ($package->getVendor() == 'keeko' && $this->isCoreSchema()) {
164
			$model = $package->getName();
165
			if ($this->hasModel($model)) {
166
				$models [] = $model;
167 1
			}
168 1
		}
169 1
		
170 1
		// anyway, generate all
171 1
		else {
172 1
			foreach ($this->getModels() as $model) {
173 1
				$models [] = $model->getOriginCommonName();
174 1
			}
175 1
		}
176 1
		
177
		return $models;
178 1
	}
179 1
	
180
	/**
181 1
	 * Checks whether the given model exists
182
	 *
183
	 * @param String $name tableName or modelName
184
	 * @return boolean
185
	 */
186
	public function hasModel($name) {
187
		return $this->getDatabase()->hasTable($this->getTableName($name), true);
188
	}
189
190
	/**
191
	 * Retrieves the model name for the given package in two steps:
192
	 * 
193
	 * 1. Check if it is passed as cli parameter
194
	 * 2. Retrieve it from the package name
195
	 *
196
	 * @return String
197
	 */
198
	public function getModelName() {
199
		$input = $this->io->getInput();
200
		$modelName = $input->hasOption('model') ? $input->getOption('model') : null;
201
		if ($modelName === null && $this->isCoreSchema()) {
202
			$package = $this->service->getPackageService()->getPackage();
203
			$packageName = $package->getName();
204
205
			if ($this->hasModel($packageName)) {
206
				$modelName = $packageName;
207
			}
208
		}
209
		return $modelName;
210
	}
211
	
212 10
	/**
213 10
	 * Parses the model name from a given action name
214 10
	 *
215 10
	 * @param ActionSchema $action
216 10
	 * @return String modelName
217 10
	 */
218 10
	public function getModelNameByAction(ActionSchema $action) {
219
		$actionName = $action->getName();
220
		$modelName = null;
221
		if (($pos = strpos($actionName, '-')) !== false) {
222
			$modelName = substr($actionName, 0, $pos);
223
		}
224
		return $modelName;
225
	}
226
227 5
	/**
228 5
	 * Returns the full model object name, including namespace
229 5
	 * 
230 5
	 * @param ActionSchema $action
231 5
	 * @return String fullModelObjectName
232
	 */
233 5
	public function getFullModelObjectName(ActionSchema $action) {
234
		$database = $this->getDatabase();
235
		$modelName = $this->getModelNameByAction($action);
236
		$model = $this->getModel($modelName);
237
		$modelObjectName = $model->getPhpName();
238
239
		return $database->getNamespace() . '\\' . $modelObjectName;
240
	}
241
	
242
	/**
243
	 * Returns the operation (verb) of the action (if existent)
244
	 * 
245
	 * @param ActionSchema $action
246
	 * @return string|null
247
	 */
248
	public function getOperationByAction(ActionSchema $action) {
249
		$actionName = $action->getName();
250
		$operation = null;
251
		if (($pos = strpos($actionName, '-')) !== false) {
252
			$operation = substr($actionName, $pos + 1);
253
		}
254
		return $operation;
255
	}
256
	
257
	/**
258
	 * Returns wether the given action refers to a model.
259
	 * 
260
	 * Examples:
261
	 * 
262 5
	 * Action: user-create => model: user
263 5
	 * Action: recover-password => no model
264 5
	 * 
265
	 * @param ActionSchema $action
266
	 * @return boolean
267
	 */
268
	public function isModelAction(ActionSchema $action) {
269
		$modelName = $this->getModelNameByAction($action);
270
		return $this->hasModel($modelName);
271
	}
272
	
273
	/**
274
	 * Returns whether this is a crud operation action
275
	 * (create, read, update, delete, list)
276
	 * 
277
	 * @param ActionSchema $action
278
	 * @return boolean
279
	 */
280
	public function isCrudAction(ActionSchema $action) {
281
		$operation = $this->getOperationByAction($action);
282
		
283
		return in_array($operation, ['create', 'read', 'update', 'delete', 'list']);
284
	}
285
	
286
	/**
287
	 * Returns all model relationships.
288
	 * 
289
	 * 
290
	 * The returned array looks like:
291
	 * [
292
	 * 		'one' => [
293
	 * 			[
294
	 * 				'type' => 'one',
295
	 * 				'fk' => $fk
296
	 * 			],
297
	 * 			[
298
	 * 				'type' => 'one',
299
	 * 				'fk' => $fk2
300
	 * 			],
301
	 * 			...
302
	 * 		],
303
	 * 		'many' => [
304
	 * 			[
305
	 * 				'type' => 'many',
306
	 * 				'fk' => $fk3,
307
	 * 				'cfk' => $cfk
308
	 * 			],
309
	 * 			[
310
	 * 				'type' => 'many',
311
	 * 				'fk' => $fk4,
312
	 * 				'cfk' => $cfk2
313
	 * 			],
314
	 * 			...
315
	 * 		],
316
	 * 		'all' => [...] // both of above
317
	 * ]
318
	 * 
319
	 * 
320
	 * @param Table $model
321
	 * @return array
322
	 */
323
	public function getRelationships(Table $model) {
324
		$all = [];
325
		$blacklist = new Set();
326
		
327
		// check if it's using concrete_inheritance behavior
328
		foreach ($model->getBehaviors() as $behavior) {
329
			if ($behavior->getName() == 'concrete_inheritance') {
330
				$blacklist->add($behavior->getParameter('extends'));
331
			}
332
		}
333
		
334
		// to-one relationships
335
		$one = [];
336
		$fks = $model->getForeignKeys();
337
		foreach ($fks as $fk) {
338
			// skip, if fk is blacklisted 
339
			if ($blacklist->contains($fk->getForeignTable()->getOriginCommonName())) {
340
				continue;
341
			}
342
343
			$item = [
344
				'type' => 'one',
345
				'fk' => $fk
346
			];
347
			$one[] = $item;
348
			$all[] = $item;
349
		}
350
	
351
		// to-many relationships
352
		$many = [];
353
		$cfks = $model->getCrossFks();
354
		foreach ($cfks as $cfk) {
355
			$foreign = null;
356
			$local = null;
357
			foreach ($cfk->getMiddleTable()->getForeignKeys() as $fk) {
358
				if ($fk->getForeignTable() != $model) {
359
					$foreign = $fk;
360
				} else if ($fk->getForeignTable() == $model) {
361
					$local = $fk;
362
				}
363
			}
364
			
365
			// skip, if fk is blacklisted
366
			if ($blacklist->contains($foreign->getForeignTable()->getOriginCommonName())) {
367
				continue;
368
			}
369
			
370
			$item = [
371
				'type' => 'many',
372
				'lk' => $local,
373
				'fk' => $foreign,
374
				'cfk' => $cfk
375
			];
376
			$many[] = $item;
377
			$all[] = $item;
378
			break;
379
		}
380
	
381
		return [
382
			'one' => $one,
383
			'many' => $many,
384
			'all' => $all,
385
			'count' => count($all)
386
		];
387
	}
388
}
389