Completed
Push — master ( e6e2aa...27549f )
by Thomas
09:03
created

ModelService   F

Complexity

Total Complexity 51

Size/Duplication

Total Lines 340
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 20

Test Coverage

Coverage 70.8%

Importance

Changes 14
Bugs 1 Features 1
Metric Value
wmc 51
c 14
b 1
f 1
lcom 2
cbo 20
dl 0
loc 340
ccs 80
cts 113
cp 0.708
rs 3.7763

18 Methods

Rating   Name   Duplication   Size   Complexity  
B getSchema() 0 28 6
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
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 46 9
A getRelationship() 0 4 1

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

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
326
		}
327
	
328
		// to-many relationships
329
		foreach ($model->getCrossFks() as $cfk) {
330
			$relationship = new ManyRelationship($model, $cfk);
331
			
332
			// skip, if fk is blacklisted
333
			if ($blacklist->contains($relationship->getForeign()->getOriginCommonName())) {
334
				continue;
335
			}
336
			
337
			$relationships->add($relationship);
338
		}
339
		
340
		$this->relationships->set($model->getName(), $relationships);
341
	
342
		return $relationships;
343
	}
344
	
345
	/**
346
	 * Returns a relationship for a given foreign name on a given model
347
	 * 
348
	 * @param Table $model
349
	 * @param string $foreignName
350
	 * @return Relationship
351
	 */
352
	public function getRelationship(Table $model, $foreignName) {
353
		$relationships = $this->getRelationships($model);
354
		return $relationships->get($foreignName);
355
	}
356
}
357