Issues (74)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/services/ModelReader.php (8 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace keeko\tools\services;
3
4
use keeko\framework\schema\GeneratorDefinitionSchema;
5
use keeko\tools\command\GenerateActionCommand;
6
use keeko\tools\command\GenerateApiCommand;
7
use keeko\tools\command\GenerateDomainCommand;
8
use keeko\tools\command\GenerateEmberModelsCommand;
9
use keeko\tools\command\GenerateSerializerCommand;
10
use keeko\tools\model\ManyToManyRelationship;
11
use keeko\tools\model\OneToManyRelationship;
12
use keeko\tools\model\OneToOneRelationship;
13
use keeko\tools\model\Project;
14
use keeko\tools\model\Relationship;
15
use keeko\tools\model\Relationships;
16
use keeko\tools\model\ReverseOneToOneRelationship;
17
use phootwork\collection\ArrayList;
18
use phootwork\collection\Map;
19
use phootwork\collection\Set;
20
use phootwork\lang\Text;
21
use Propel\Generator\Builder\Util\SchemaReader;
22
use Propel\Generator\Config\GeneratorConfig;
23
use Propel\Generator\Model\Database;
24
use Propel\Generator\Model\Table;
25
26
class ModelReader {
27
28
	/** @var Project */
29
	private $project;
30
31
	/** @var Database */
32
	private $database;
33
34
	/** @var GeneratorDefinitionSchema */
35
	private $generatorDefinition;
36
37
	/** @var Map */
38
	private $relationships;
39
40
	/** @var Set */
41
	private $relationshipsLoaded;
42
43
	/** @var Map */
44
	private $models;
45
46
	/** @var Set */
47
	private $excluded;
48
49
	/** @var CommandService */
50
	private $service;
51
52
	public function __construct(Project $project, CommandService $service) {
53
		$this->project = $project;
54
		$this->service = $service;
55
		$this->relationships = new Map();
56
		$this->relationshipsLoaded = new Set();
57
		$this->generatorDefinition = $project->getGeneratorDefinition();
58
		$this->excluded = $this->loadExcludedModels();
59
60
		$this->load();
61
	}
62
63
	private function loadExcludedModels() {
64
		$list = new ArrayList();
65
		$command = $this->service->getCommand();
66
		if ($command instanceof GenerateActionCommand) {
67
			$list = $this->generatorDefinition->getExcludedAction();
68
		} else if ($command instanceof GenerateApiCommand) {
69
			$list = $this->generatorDefinition->getExcludedApi();
70
		} else if ($command instanceof GenerateDomainCommand) {
71
			$list = $this->generatorDefinition->getExcludedDomain();
72
		} else if ($command instanceof GenerateEmberModelsCommand) {
73
			$list = $this->generatorDefinition->getExcludedEmber();
74
		} else if ($command instanceof GenerateSerializerCommand) {
75
			$list = $this->generatorDefinition->getExcludedSerializer();
76
		}
77
78
		return new Set($list);
79
	}
80
81
	public function getExcluded() {
82
		return $this->excluded;
83
	}
84
85
	public function getProject() {
86
		return $this->project;
87
	}
88
89
	private function load() {
90
		if ($this->project->hasSchemaFile()) {
91
			$this->loadDatabase();
92
			$this->loadRelationships();
93
		}
94
	}
95
96
	private function loadDatabase() {
0 ignored issues
show
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...
97
		if ($this->database === null) {
98
			$dom = new \DOMDocument('1.0', 'UTF-8');
99
			$dom->load($this->project->getSchemaFileName());
100
			$this->includeExternalSchemas($dom);
101
102
			$config = new GeneratorConfig($this->project->getRootPath());
103
			$reader = new SchemaReader($config->getConfiguredPlatform());
104
			$reader->setGeneratorConfig($config);
105
			$schema = $reader->parseString($dom->saveXML(), $this->project->getSchemaFileName());
106
			$this->database = $schema->getDatabase();
107
108
			// extend excluded list with parents when using a certain behavior
109
			foreach ($this->database->getTables() as $table) {
110
				foreach ($table->getBehaviors() as $behavior) {
111
112
					switch ($behavior->getName()) {
113
						case 'concrete_inheritance':
114
							$parent = $behavior->getParameter('extends');
115
							$this->excluded->add($parent);
116
							$this->renameForeignKeys($table, $parent);
117
							break;
118
119
						case 'versionable':
120
							$versionTableName = $behavior->getParameter('version_table')
121
								? $behavior->getParameter('version_table')
122
								: ($table->getOriginCommonName() . '_version');
123
124
							$this->excluded->add($versionTableName);
125
							break;
126
					}
127
				}
128
			}
129
		}
130
131
		return $this->database;
132
	}
133
134
	private function renameForeignKeys(Table $table, $parent) {
135
		$parent = $this->getModel($parent);
136
137
		foreach ($table->getForeignKeys() as $fk) {
138
			// find fk in parent
139
			foreach ($parent->getForeignKeys() as $pfk) {
140
				if ($pfk->getForeignTableCommonName() == $fk->getForeignTableCommonName()
141
						&& $pfk->getLocalColumnName() == $fk->getLocalColumnName()
142
						&& $pfk->getForeignColumnName() == $fk->getForeignColumnName()) {
143
144
					// replace
145
					$name = new Text($pfk->getName());
146
					if ($name->contains($parent->getOriginCommonName())) {
147
						$name = $name->replace($parent->getOriginCommonName(), $table->getOriginCommonName());
148
						$fk->setName($name->toString());
149
					}
150
					break;
151
				}
152
			}
153
		}
154
	}
155
156
	private function includeExternalSchemas(\DOMDocument $dom) {
157
		$databaseNode = $dom->getElementsByTagName('database')->item(0);
158
		$externalSchemaNodes = $dom->getElementsByTagName('external-schema');
159
160
		while ($externalSchema = $externalSchemaNodes->item(0)) {
161
			$include = $externalSchema->getAttribute('filename');
162
			$externalSchema->parentNode->removeChild($externalSchema);
163
164
			if (!is_readable($include)) {
165
				throw new \RuntimeException("External schema '$include' does not exist");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $include instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
166
			}
167
168
			$externalSchemaDom = new \DOMDocument('1.0', 'UTF-8');
169
			$externalSchemaDom->load(realpath($include));
170
171
			// The external schema may have external schemas of its own ; recurs
172
			$this->includeExternalSchemas($externalSchemaDom);
173
			foreach ($externalSchemaDom->getElementsByTagName('table') as $tableNode) {
174
				$databaseNode->appendChild($dom->importNode($tableNode, true));
175
			}
176
		}
177
	}
178
179
	public function getDatabase() {
180
		return $this->database;
181
	}
182
183
	private function loadRelationships() {
184
		foreach ($this->getModels() as $table) {
185
			$this->loadRelationshipsForModel($table);
186
		}
187
	}
188
189
	/**
190
	 * Returns all model relationships.
191
	 *
192
	 * @param Table $model
193
	 */
194
	private function loadRelationshipsForModel(Table $model) {
195
		if ($this->relationshipsLoaded->contains($model->getName())) {
196
			return;
197
		}
198
199
		if ($this->excluded->contains($model->getOriginCommonName())) {
200
			return;
201
		}
202
203
		$relationships = $this->getRelationships($model);
204
		$definition = $this->generatorDefinition->getRelationships($model->getOriginCommonName());
205
206
		// one-to-* relationships
207
		foreach ($model->getForeignKeys() as $fk) {
208
			// skip, if fk is excluded
209
			if ($this->excluded->contains($fk->getForeignTable()->getOriginCommonName())) {
210
				continue;
211
			}
212
213
			$type = Relationship::ONE_TO_MANY;
214
			if ($definition->has($fk->getName())) {
215
				$type = $definition->get($fk->getName());
216
			}
217
218
			$foreign = $fk->getForeignTable();
219
220
			switch ($type) {
221
				case Relationship::ONE_TO_ONE:
222
					$relationship = new OneToOneRelationship($model, $foreign, $fk);
0 ignored issues
show
It seems like $foreign defined by $fk->getForeignTable() on line 218 can be null; however, keeko\tools\model\OneToO...tionship::__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...
223
					$relationships->add($relationship);
224
225
					$reverse = new ReverseOneToOneRelationship($foreign, $model, $fk);
0 ignored issues
show
It seems like $foreign defined by $fk->getForeignTable() on line 218 can be null; however, keeko\tools\model\OneToO...tionship::__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...
226
					$relationship->setReverseRelationship($reverse);
227
					$this->getRelationships($foreign)->add($reverse);
0 ignored issues
show
It seems like $foreign defined by $fk->getForeignTable() on line 218 can be null; however, keeko\tools\services\Mod...der::getRelationships() 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...
228
					break;
229
230
				case Relationship::ONE_TO_MANY:
231
					$relationship = new OneToManyRelationship($foreign, $model, $fk);
0 ignored issues
show
It seems like $foreign defined by $fk->getForeignTable() on line 218 can be null; however, keeko\tools\model\OneToO...tionship::__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...
232
					$this->getRelationships($foreign)->add($relationship);
0 ignored issues
show
It seems like $foreign defined by $fk->getForeignTable() on line 218 can be null; however, keeko\tools\services\Mod...der::getRelationships() 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...
233
234
					$reverse = new OneToOneRelationship($model, $foreign, $fk);
0 ignored issues
show
It seems like $foreign defined by $fk->getForeignTable() on line 218 can be null; however, keeko\tools\model\OneToO...tionship::__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...
235
					$relationship->setReverseRelationship($reverse);
236
					$relationships->add($reverse);
237
					break;
238
			}
239
		}
240
241
		// many-to-many relationships
242
		foreach ($model->getCrossFks() as $cfk) {
243
			$relationship = new ManyToManyRelationship($model, $cfk);
244
245
			// skip, if fk is excluded
246
			if ($this->excluded->contains($relationship->getForeign()->getOriginCommonName())) {
247
				continue;
248
			}
249
250
			$relationships->add($relationship);
251
		}
252
253
		$this->relationships->set($model->getName(), $relationships);
254
		$this->relationshipsLoaded->add($model->getName());
255
256
		return $relationships;
257
	}
258
259
	/**
260
	 *
261
	 * @param Table $model
262
	 * @return Relationships
263
	 */
264
	public function getRelationships(Table $model) {
265
		if (!$this->relationships->has($model->getName())) {
266
			$this->relationships->set($model->getName(), new Relationships($model));
267
		}
268
		return $this->relationships->get($model->getName());
269
	}
270
271
	/**
272
	 * Checks whether the given model exists
273
	 *
274
	 * @param String $name tableName or modelName
275
	 * @return boolean
276
	 */
277
	public function hasModel($name) {
278
		return $this->getDatabase()->hasTable($this->getTableName($name), true);
279
	}
280
281
	/**
282
	 * Returns the model for the given name
283
	 *
284
	 * @param String $name modelName or tableName
285
	 * @return Table
286
	 */
287
	public function getModel($name) {
288
		$tableName = $this->getTableName($name);
289
		$db = $this->getDatabase();
290
		$table = $db->getTable($tableName);
291
292
		return $table;
293
	}
294
295
	/**
296
	 * Returns the tableName for a given name
297
	 *
298
	 * @param String $name tableName or modelName
299
	 * @return String tableName
300
	 */
301
	public function getTableName($name) {
302
		$db = $this->getDatabase();
303
		if (!Text::create($name)->startsWith($db->getTablePrefix())) {
304
			$name = $db->getTablePrefix() . $name;
305
		}
306
307
		return $name;
308
	}
309
310
	/**
311
	 * Returns the models from the database, where table namespace matches package namespace
312
	 *
313
	 * @return Map
314
	 */
315
	public function getModels() {
316
		if ($this->models === null) {
317
			$database = $this->getDatabase();
318
			$namespace = $database->getNamespace();
319
320
			$this->models = new Map();
321
322
			foreach ($database->getTables() as $table) {
323
				if (!$table->isCrossRef()
324
						&& $table->getNamespace() == $namespace
325
						&& !$this->excluded->contains($table->getOriginCommonName())) {
326
					$this->models->set($table->getOriginCommonName(), $table);
327
				}
328
			}
329
		}
330
331
		return $this->models;
332
	}
333
334
	/**
335
	 * Returns all model names
336
	 *
337
	 * @return Set
338
	 */
339
	public function getModelNames() {
340
		return $this->getModels()->keys();
341
	}
342
}