Completed
Push — master ( 98cd2e...56265b )
by Thomas
09:01
created

ModelService::getRelationships()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 46
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 46
ccs 0
cts 0
cp 0
rs 8.4751
cc 6
eloc 33
nc 4
nop 1
crap 42
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
12
class ModelService extends AbstractService {
13
14
	private $models = null;
15
	private $schema = null;
16
	
17
	/** @var Database */
18
	private $database = null;
19
20
	/**
21
	 * Returns the propel schema. The three locations, where the schema is looked up in:
22
	 *
23
	 * 1. --schema option (if available)
24
	 * 2. database/schema.xml
25
	 * 3. core/database/schema.xml
26
	 *
27
	 * @throws \RuntimeException
28
	 * @return string the path to the schema
29
	 */
30
	public function getSchema() {
31 12
		$input = $this->io->getInput();
32 12
		if ($this->schema === null) {
33 12
			$workDir = $this->service->getProject()->getRootPath();
34 12
			$schema = null;
35 12
			$schemas = [
36
				$input->hasOption('schema') ? $input->getOption('schema') : '',
37 12
				$workDir . '/database/schema.xml',
38 12
				$workDir . '/core/database/schema.xml',
39
				$workDir . '/vendor/keeko/core/database/schema.xml'
40 12
			];
41 12
			foreach ($schemas as $path) {
42 12
				if (file_exists($path)) {
43 12
					$schema = $path;
44 12
					break;
45
				}
46 12
			}
47 12
			$this->schema = $schema;
48
				
49 12
			if ($schema === null) {
50
				$locations = implode(', ', $schemas);
51
				throw new \RuntimeException(sprintf('Can\'t find schema in these locations: %s', $locations));
52
			}
53 12
		}
54
55 12
		return $this->schema;
56
	}
57
	
58 3
	public function isCoreSchema() {
59 3
		return strpos($this->getSchema(), 'core') !== false;
60
	}
61
	
62
	public function hasSchema() {
63
		$vendorName = $this->packageService->getPackage()->getVendor();
64
		return $this->getSchema() !== null && ($this->isCoreSchema() ? $vendorName == 'keeko' : true);
65
	}
66
	
67
	/**
68
	 * Returns the propel database
69
	 *
70
	 * @return Database
71
	 */
72 12
	public function getDatabase() {
73 12
		if ($this->database === null) {
74 12
			$builder = new QuickBuilder();
75 12
			$builder->setSchema(file_get_contents($this->getSchema()));
76 12
			$this->database = $builder->getDatabase();
77 12
		}
78
	
79 12
		return $this->database;
80
	}
81
	
82
	/**
83
	 * Returns the tableName for a given name
84
	 *
85
	 * @param String $name tableName or modelName
86
	 * @return String tableName
87
	 */
88 11
	public function getTableName($name) {
89 11
		$db = $this->getDatabase();
90 11
		if (!Text::create($name)->startsWith($db->getTablePrefix())) {
91 11
			$name = $db->getTablePrefix() . $name;
92 11
		}
93
	
94 11
		return $name;
95
	}
96
	
97
	/**
98
	 * Returns all model names
99
	 *
100
	 * @return String[] an array of modelName
101
	 */
102
	public function getModelNames() {
103
		$names = [];
104
		$database = $this->getDatabase();
105
		foreach ($database->getTables() as $table) {
106
			$names[] = $table->getOriginCommonName();
107
		}
108
	
109
		return $names;
110
	}
111
	
112
	/**
113
	 * Returns the propel models from the database, where table namespace matches package namespace
114
	 *
115
	 * @return ArrayList<Table>
1 ignored issue
show
Documentation introduced by
The doc-type ArrayList<Table> could not be parsed: Expected "|" or "end of type", but got "<" at position 9. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
116
	 */
117 1
	public function getModels() {
1 ignored issue
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
118 1
		if ($this->models === null) {
119 1
			$namespace = $this->packageService->getNamespace() . '\\model';
120 1
			$propel = $this->getDatabase();
121
	
122 1
			$this->models = new ArrayList();
123
	
124 1
			foreach ($propel->getTables() as $table) {
125 1
				if (!$table->isCrossRef() && $table->getNamespace() == $namespace) {
126 1
					$this->models->add($table);
127 1
				}
128 1
			}
129 1
		}
130
	
131 1
		return $this->models;
132
	}
133
134
	/**
135
	 * Returns the model for the given name
136
	 *
137
	 * @param String $name modelName or tableName
138
	 * @return Table
139
	 */
140 7
	public function getModel($name) {
141 7
		$tableName = $this->getTableName($name);
142 7
		$db = $this->getDatabase();
143
// 		echo $db->getNamespace();
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
144
// 		foreach ($db->getTables() as $table) {
145
// 			echo $table->getName();
146
// 		}
147 7
		$table = $db->getTable($tableName);
148
	
149 7
		return $table;
150
	}
151
	
152
	/**
153
	 * Returns the model names for a given package
154
	 * 
155
	 * @param PackageSchema $package a package to search models for, if omitted global package is used
156
	 * @return array array with string of model names
157
	 */
158 8
	public function getPackageModelNames(PackageSchema $package = null) {
159 8
		if ($package === null) {
160
			$package = $this->packageService->getPackage();
161
		}
162
		
163
		$models = [];
164
		// if this is a core-module, find the related model
165
		if ($package->getVendor() == 'keeko' && $this->isCoreSchema()) {
166
			$model = $package->getName();
167 1
			if ($this->hasModel($model)) {
168 1
				$models []= $model;
169 1
			}
170 1
		}
171 1
		
172 1
		// anyway, generate all
173 1
		else {
174 1
			foreach ($this->getModels() as $model) {
175 1
				$models []= $model->getOriginCommonName();
176 1
			}
177
		}
178 1
		
179 1
		return $models;
180
	}
181 1
	
182
	/**
183
	 * Checks whether the given model exists
184
	 *
185
	 * @param String $name tableName or modelName
186
	 * @return boolean
187
	 */
188
	public function hasModel($name) {
189
		return $this->getDatabase()->hasTable($this->getTableName($name), true);
190
	}
191
192
	/**
193
	 * Retrieves the model name for the given package in two steps:
194
	 * 
195
	 * 1. Check if it is passed as cli parameter
196
	 * 2. Retrieve it from the package name
197
	 *
198
	 * @return String
199
	 */
200
	public function getModelName() {
201
		$input = $this->io->getInput();
202
		$modelName = $input->hasOption('model') ? $input->getOption('model') : null;
203
		if ($modelName === null && $this->isCoreSchema()) {
204
			$package = $this->service->getPackageService()->getPackage();
205
			$packageName = $package->getName();
206
207
			if ($this->hasModel($packageName)) {
208
				$modelName = $packageName;
209
			}
210
		}
211
		return $modelName;
212 10
	}
213 10
	
214 10
	/**
215 10
	 * Parses the model name from a given action name
216 10
	 *
217 10
	 * @param ActionSchema $action
218 10
	 * @return String modelName
219
	 */
220
	public function getModelNameByAction(ActionSchema $action) {
221
		$actionName = $action->getName();
222
		$modelName = null;
223
		if (($pos = strpos($actionName, '-')) !== false) {
224
			$modelName = substr($actionName, 0, $pos);
225
		}
226
		return $modelName;
227 5
	}
228 5
229 5
	/**
230 5
	 * Returns the full model object name, including namespace
231 5
	 * 
232
	 * @param ActionSchema $action
233 5
	 * @return String fullModelObjectName
234
	 */
235
	public function getFullModelObjectName(ActionSchema $action) {
236
		$database = $this->getDatabase();
237
		$modelName = $this->getModelNameByAction($action);
238
		$model = $this->getModel($modelName);
239
		$modelObjectName = $model->getPhpName();
240
241
		return $database->getNamespace() . '\\' . $modelObjectName;
242
	}
243
	
244
	/**
245
	 * Returns the operation (verb) of the action (if existent)
246
	 * 
247
	 * @param ActionSchema $action
248
	 * @return string|null
249
	 */
250
	public function getOperationByAction(ActionSchema $action) {
251
		$actionName = $action->getName();
252
		$operation = null;
253
		if (($pos = strpos($actionName, '-')) !== false) {
254
			$operation = substr($actionName, $pos + 1);
255
		}
256
		return $operation;
257
	}
258
	
259
	/**
260
	 * Returns wether the given action refers to a model.
261
	 * 
262 5
	 * Examples:
263 5
	 * 
264 5
	 * Action: user-create => model: user
265
	 * Action: recover-password => no model
266
	 * 
267
	 * @param ActionSchema $action
268
	 * @return boolean
269
	 */
270
	public function isModelAction(ActionSchema $action) {
271
		$modelName = $this->getModelNameByAction($action);
272
		return $this->hasModel($modelName);
273
	}
274
	
275
	/**
276
	 * Returns whether this is a crud operation action
277
	 * (create, read, update, delete, list)
278
	 * 
279
	 * @param ActionSchema $action
280
	 * @return boolean
281
	 */
282
	public function isCrudAction(ActionSchema $action) {
283
		$operation = $this->getOperationByAction($action);
284
		
285
		return in_array($operation, ['create', 'read', 'update', 'delete', 'list']);
286
	}
287
	
288
	/**
289
	 * Returns all model relationships.
290
	 * 
291
	 * 
292
	 * The returned array looks like:
293
	 * [
294
	 * 		'one' => [
295
	 * 			[
296
	 * 				'type' => 'one',
297
	 * 				'fk' => $fk
298
	 * 			],
299
	 * 			[
300
	 * 				'type' => 'one',
301
	 * 				'fk' => $fk2
302
	 * 			],
303
	 * 			...
304
	 * 		],
305
	 * 		'many' => [
306
	 * 			[
307
	 * 				'type' => 'many',
308
	 * 				'fk' => $fk3,
309
	 * 				'cfk' => $cfk
310
	 * 			],
311
	 * 			[
312
	 * 				'type' => 'many',
313
	 * 				'fk' => $fk4,
314
	 * 				'cfk' => $cfk2
315
	 * 			],
316
	 * 			...
317
	 * 		],
318
	 * 		'all' => [...] // both of above
319
	 * ]
320
	 * 
321
	 * 
322
	 * @param Table $model
323
	 * @return array
324
	 */
325
	public function getRelationships(Table $model) {
326
		$all = [];
327
		
328
		// to-one relationships
329
		$one = [];
330
		$fks = $model->getForeignKeys();
331
		foreach ($fks as $fk) {
332
			$item = [
333
				'type' => 'one',
334
				'fk' => $fk
335
			];
336
			$one[] = $item;
337
			$all[] = $item;
338
		}
339
	
340
		// to-many relationships
341
		$many = [];
342
		$cfks = $model->getCrossFks();
343
		foreach ($cfks as $cfk) {
344
			$foreign = null;
345
			$local = null;
346
			foreach ($cfk->getMiddleTable()->getForeignKeys() as $fk) {
347
				if ($fk->getForeignTable() != $model) {
348
					$foreign = $fk;
349
				} else if ($fk->getForeignTable() == $model) {
350
					$local = $fk;
351
				}
352
			}
353
			$item = [
354
				'type' => 'many',
355
				'lk' => $local,
356
				'fk' => $foreign,
357
				'cfk' => $cfk
358
			];
359
			$many[] = $item;
360
			$all[] = $item;
361
			break;
362
		}
363
	
364
		return [
365
			'one' => $one,
366
			'many' => $many,
367
			'all' => $all,
368
			'count' => count($all)
369
		];
370
	}
371
}
372