Completed
Push — master ( 915477...e8ce9b )
by Jean-Christophe
01:34
created

DAO::getManyToManyFromArray()   C

Complexity

Conditions 8
Paths 24

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 5.7377
c 0
b 0
f 0
cc 8
eloc 17
nc 24
nop 5
1
<?php
2
3
namespace Ubiquity\orm;
4
5
use Ubiquity\db\SqlUtils;
6
use Ubiquity\db\Database;
7
use Ubiquity\log\Logger;
8
use Ubiquity\orm\parser\ManyToManyParser;
9
use Ubiquity\orm\parser\Reflexion;
10
use Ubiquity\utils\JArray;
11
12
/**
13
 * Classe passerelle entre base de données et modèle objet
14
 * @author jc
15
 * @version 1.0.0.5
16
 * @package orm
17
 */
18
class DAO {
19
	public static $db;
20
21
	private static function getCondition($keyValues,$classname=NULL) {
22
		$retArray=array ();
23
		if (is_array($keyValues)) {
24
			if(!JArray::isAssociative($keyValues)){
25
				if(isset($classname)){
26
					$keys=OrmUtils::getKeyFields($classname);
27
					$keyValues=\array_combine($keys, $keyValues);
28
				}
29
			}
30
			foreach ( $keyValues as $key => $value ) {
31
				$retArray[]="`" . $key . "` = '" . $value . "'";
32
			}
33
			$condition=implode(" AND ", $retArray);
34
		} else
35
			$condition=$keyValues;
36
		return $condition;
37
	}
38
39
	/**
40
	 * Charge les membres associés à $instance par une relation de type ManyToOne
41
	 * @param object $instance
42
	 * @param mixed $value
43
	 * @param array $annotationArray
44
	 * @param boolean $useCache
45
	 */
46
	private static function getOneManyToOne($instance, $value, $annotationArray, $useCache=NULL) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
47
		$class=get_class($instance);
48
		$member=$annotationArray["member"];
49
		$key=OrmUtils::getFirstKey($annotationArray["className"]);
50
		$kv=array ($key => $value );
51
		$obj=self::getOne($annotationArray["className"], $kv, false, false, $useCache);
52
		if ($obj !== null) {
53
			Logger::log("getOneManyToOne", "Chargement de " . $member . " pour l'objet " . $class);
54
			$accesseur="set" . ucfirst($member);
55
			if (method_exists($instance, $accesseur)) {
56
				$instance->$accesseur($obj);
57
				$instance->_rest[$member]=$obj->_rest;
58
				return;
59
			}
60
		}
61
	}
62
63
	private static function _getOneToManyFromArray(&$ret, $array, $fkv, $mappedBy) {
64
		$elementAccessor="get" . ucfirst($mappedBy);
65
		foreach ( $array as $element ) {
66
			$elementRef=$element->$elementAccessor();
67
			if (!is_null($elementRef)) {
68
				$idElementRef=OrmUtils::getFirstKeyValue($elementRef);
69
				if ($idElementRef == $fkv)
70
					$ret[]=$element;
71
			}
72
		}
73
	}
74
75
	/**
76
	 * Affecte/charge les enregistrements fils dans le membre $member de $instance.
77
	 * Si $array est null, les fils sont chargés depuis la base de données
78
	 * @param object $instance
79
	 * @param string $member Membre sur lequel doit être présent une annotation OneToMany
80
	 * @param boolean $useCache
81
	 * @param array $annot used internally
82
	 */
83
	public static function getOneToMany($instance, $member, $useCache=NULL, $annot=null) {
84
		$ret=array ();
85
		$class=get_class($instance);
86
		if (!isset($annot))
87
			$annot=OrmUtils::getAnnotationInfoMember($class, "#oneToMany", $member);
88
			if ($annot !== false) {
89
				$fkAnnot=OrmUtils::getAnnotationInfoMember($annot["className"], "#joinColumn", $annot["mappedBy"]);
90
				if ($fkAnnot !== false) {
91
					$fkv=OrmUtils::getFirstKeyValue($instance);
92
					$ret=self::getAll($annot["className"], $fkAnnot["name"] . "='" . $fkv . "'", true, false, $useCache);
93
					self::setToMember($member, $instance, $ret, $class, "getOneToMany");
94
				}
95
			}
96
			return $ret;
97
	}
98
99
	public static function affectsOneToManyFromArray($instance, $member, $array=null, $mappedBy=null) {
100
		$ret=array ();
101
		$class=get_class($instance);
102
		if (!isset($mappedBy)){
103
			$annot=OrmUtils::getAnnotationInfoMember($class, "#oneToMany", $member);
104
			$mappedBy=$annot["mappedBy"];
105
		}
106
		if ($mappedBy !== false) {
107
				$fkv=OrmUtils::getFirstKeyValue($instance);
108
				self::_getOneToManyFromArray($ret, $array, $fkv, $mappedBy);
109
				self::setToMember($member, $instance, $ret, $class, "getOneToMany");
110
		}
111
		return $ret;
112
	}
113
114
	private static function setToMember($member, $instance, $value, $class, $part) {
115
		$accessor="set" . ucfirst($member);
116
		if (method_exists($instance, $accessor)) {
117
			Logger::log($part, "Affectation de " . $member . " pour l'objet " . $class);
118
			$instance->$accessor($value);
119
			$instance->_rest[$member]=$value;
120
		} else {
121
			Logger::warn($part, "L'accesseur " . $accessor . " est manquant pour " . $class);
122
		}
123
	}
124
125
	/**
126
	 *
127
	 * @param object $instance
128
	 * @param ManyToManyParser $parser
129
	 * @return PDOStatement
130
	 */
131
	private static function getSQLForJoinTable($instance, ManyToManyParser $parser) {
132
		$accessor="get" . ucfirst($parser->getPk());
133
		$sql="SELECT * FROM `" . $parser->getJoinTable() . "` WHERE `" . $parser->getMyFkField() . "`='" . $instance->$accessor() . "'";
134
		Logger::log("ManyToMany", "Exécution de " . $sql);
135
		return self::$db->query($sql);
136
	}
137
138
	/**
139
	 * Affecte/charge les enregistrements fils dans le membre $member de $instance.
140
	 * Si $array est null, les fils sont chargés depuis la base de données
141
	 * @param object $instance
142
	 * @param string $member Membre sur lequel doit être présent une annotation ManyToMany
143
	 * @param array $array paramètre facultatif contenant la liste des fils possibles
144
	 * @param boolean $useCache
145
	 */
146
	public static function getManyToMany($instance, $member, $array=null, $useCache=NULL) {
147
		$ret=array ();
148
		$class=get_class($instance);
149
		$parser=new ManyToManyParser($instance, $member);
150
		if ($parser->init()) {
151
			if (is_null($array)) {
152
				$joinTableCursor=self::getSQLForJoinTable($instance, $parser);
153
				foreach ( $joinTableCursor as $row ) {
154
					$fkv=$row[$parser->getFkField()];
155
					$tmp=self::getOne($parser->getTargetEntity(), "`" . $parser->getPk() . "`='" . $fkv . "'", false, false, $useCache);
156
					array_push($ret, $tmp);
157
				}
158
			} else {
159
				self::getManyToManyFromArray($ret, $instance, $array, $class, $parser);
160
			}
161
			self::setToMember($member, $instance, $ret, $class, "getManyToMany");
162
		}
163
		return $ret;
164
	}
165
166
	private static function getManyToManyFromArray(&$ret, $instance, $array, $class, $parser) {
167
		$continue=true;
168
		$accessorToMember="get" . ucfirst($parser->getInversedBy());
169
		$myPkAccessor="get" . ucfirst($parser->getMyPk());
170
171
		if (!method_exists($instance, $myPkAccessor)) {
172
			Logger::warn("ManyToMany", "L'accesseur au membre clé primaire " . $myPkAccessor . " est manquant pour " . $class);
173
		}
174
		if (count($array) > 0)
175
			$continue=method_exists($array[0], $accessorToMember);
176
		if ($continue) {
177
			foreach ( $array as $targetEntityInstance ) {
178
				$instances=$targetEntityInstance->$accessorToMember();
179
				if (is_array($instances)) {
180
					foreach ( $instances as $inst ) {
181
						if ($inst->$myPkAccessor() == $instance->$myPkAccessor())
182
							array_push($ret, $targetEntityInstance);
183
					}
184
				}
185
			}
186
		} else {
187
			Logger::warn("ManyToMany", "L'accesseur au membre " . $parser->getInversedBy() . " est manquant pour " . $parser->getTargetEntity());
188
		}
189
	}
190
191
	/**
192
	 * Retourne un tableau d'objets de $className depuis la base de données
193
	 * @param string $className nom de la classe du model à charger
194
	 * @param string $condition Partie suivant le WHERE d'une instruction SQL
195
	 * @param boolean $loadManyToOne
196
	 * @param boolean $loadOneToMany
197
	 * @param boolean $useCache
198
	 * @return array
199
	 */
200
	public static function getAll($className, $condition='', $loadManyToOne=true, $loadOneToMany=false, $useCache=NULL) {
201
		$objects=array ();
202
		$invertedJoinColumns=null;
203
		$oneToManyFields=null;
204
		$metaDatas=OrmUtils::getModelMetadata($className);
205
		$tableName=$metaDatas["#tableName"];
206
		if ($loadManyToOne && isset($metaDatas["#invertedJoinColumn"]))
207
			$invertedJoinColumns=$metaDatas["#invertedJoinColumn"];
208
		if ($loadOneToMany && isset($metaDatas["#oneToMany"])) {
209
			$oneToManyFields=$metaDatas["#oneToMany"];
210
		}
211
		if ($condition != ''){
212
			$condition=" WHERE " . $condition;
213
		}
214
		$members=\array_diff($metaDatas["#fieldNames"],$metaDatas["#notSerializable"]);
215
		$query=self::$db->prepareAndExecute($tableName, $condition,$members,$useCache);
216
		Logger::log("getAll", "SELECT * FROM " . $tableName . $condition);
217
		$oneToManyQueries=[];
218
		$manyToOneQueries=[];
219
220
		foreach ( $query as $row ) {
221
			$object=self::loadObjectFromRow($row, $className, $invertedJoinColumns, $oneToManyFields,$members, $oneToManyQueries,$manyToOneQueries,$useCache);
222
			$key=OrmUtils::getFirstKeyValue($object);
223
			$objects[$key]=$object;
224
		}
225
226 View Code Duplication
		if($loadManyToOne && \sizeof($manyToOneQueries)>0){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
227
			foreach ($manyToOneQueries as $key=>$conditions){
228
				list($fkClass,$member,$fkField)=\explode("|", $key);
229
				$condition=\implode(" OR ", $conditions);
230
				$manyToOneObjects=self::getAll($fkClass,$condition,true,false,$useCache);
231
				foreach ($objects as $object){
232
					self::affectsManyToOneFromArray($object,$member,$manyToOneObjects,$fkField);
233
				}
234
			}
235
		}
236
237 View Code Duplication
		if($loadOneToMany && \sizeof($oneToManyQueries)>0){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
238
			foreach ($oneToManyQueries as $key=>$conditions){
239
				list($class,$member,$mappedBy)=\explode("|", $key);
240
				$condition=\implode(" OR ", $conditions);
241
				$manyObjects=self::getAll($class,$condition,true,false,$useCache);
242
				foreach ($objects as $object){
243
					self::affectsOneToManyFromArray($object, $member,$manyObjects,$mappedBy);
244
				}
245
			}
246
		}
247
		return $objects;
248
	}
249
250
	private static function affectsManyToOneFromArray($object,$member,$manyToOneObjects,$fkField){
251
		$class=\get_class($object);
252
		if(isset($object->$fkField)){
253
			$value=$manyToOneObjects[$object->$fkField];
254
			self::setToMember($member, $object, $value, $class, "getManyToOne");
255
		}
256
	}
257
258
	private static function loadObjectFromRow($row, $className, $invertedJoinColumns, $oneToManyFields, $members,&$oneToManyQueries,&$manyToOneQueries,$useCache=NULL) {
0 ignored issues
show
Unused Code introduced by
The parameter $useCache is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
259
		$o=new $className();
260
		foreach ( $row as $k => $v ) {
261
			if(($field=\array_search($k, $members))!==false){
262
				$accesseur="set" . ucfirst($field);
263
				if (method_exists($o, $accesseur)) {
264
					$o->$accesseur($v);
265
					$o->_rest[$field]=$v;
266
				}
267
			}
268
			if (isset($invertedJoinColumns) && isset($invertedJoinColumns[$k])) {
269
				$fk="_".$k;
270
				$o->$fk=$v;
271
				self::prepareManyToOne($manyToOneQueries,$v, $fk,$invertedJoinColumns[$k]);
272
			}
273
		}
274
		if (isset($oneToManyFields)) {
275
			foreach ( $oneToManyFields as $k => $annot ) {
276
				self::prepareOneToMany($oneToManyQueries,$o, $k, $annot);
277
			}
278
		}
279
		return $o;
280
	}
281
282
283
	/**
284
	 * Prépare les enregistrements fils dans le membre $member de $instance.
285
	 * Si $array est null, les fils sont chargés depuis la base de données
286
	 * @param $ret array of sql
287
	 * @param object $instance
288
	 * @param string $member Membre sur lequel doit être présent une annotation OneToMany
289
	 * @param array $annot used internally
290
	 */
291
	private static function prepareOneToMany(&$ret,$instance, $member, $annot=null) {
292
		$class=get_class($instance);
293
		if (!isset($annot))
294
			$annot=OrmUtils::getAnnotationInfoMember($class, "#oneToMany", $member);
295
			if ($annot !== false) {
296
				$fkAnnot=OrmUtils::getAnnotationInfoMember($annot["className"], "#joinColumn", $annot["mappedBy"]);
297
				if ($fkAnnot !== false) {
298
					$fkv=OrmUtils::getFirstKeyValue($instance);
299
					$key=$annot["className"]."|".$member."|".$annot["mappedBy"];
300
					if(!isset($ret[$key])){
301
						$ret[$key]=[];
302
					}
303
					$ret[$key][$fkv]=$fkAnnot["name"] . "='" . $fkv . "'";
304
				}
305
			}
306
	}
307
308
	/**
309
	 * Prépare les membres associés à $instance par une relation de type ManyToOne
310
	 * @param $ret array of sql
311
	 * @param mixed $value
312
	 * @param string $fkField
313
	 * @param array $annotationArray
314
	 */
315
	private static function prepareManyToOne(&$ret, $value, $fkField,$annotationArray) {
316
		$member=$annotationArray["member"];
317
		$fk=OrmUtils::getFirstKey($annotationArray["className"]);
318
		$key=$annotationArray["className"]."|".$member."|".$fkField;
319
		if(!isset($ret[$key])){
320
			$ret[$key]=[];
321
		}
322
		$ret[$key][$value]=$fk . "='" . $value . "'";
323
	}
324
325
	/**
326
	 * Retourne le nombre d'objets de $className depuis la base de données respectant la condition éventuellement passée en paramètre
327
	 * @param string $className nom de la classe du model à charger
328
	 * @param string $condition Partie suivant le WHERE d'une instruction SQL
329
	 */
330
	public static function count($className, $condition='') {
331
		$tableName=OrmUtils::getTableName($className);
332
		if ($condition != '')
333
			$condition=" WHERE " . $condition;
334
		return self::$db->query("SELECT COUNT(*) FROM " . $tableName . $condition)->fetchColumn();
335
	}
336
337
	/**
338
	 * Retourne une instance de $className depuis la base de données, à  partir des valeurs $keyValues de la clé primaire
339
	 * @param String $className nom de la classe du model à charger
340
	 * @param Array|string $keyValues valeurs des clés primaires ou condition
341
	 * @param boolean $useCache
342
	 */
343
	public static function getOne($className, $keyValues, $loadManyToOne=true, $loadOneToMany=false, $useCache=NULL) {
344
		if (!is_array($keyValues)) {
345
			if (strrpos($keyValues, "=") === false) {
346
				$keyValues="`" . OrmUtils::getFirstKey($className) . "`='" . $keyValues . "'";
347
			} elseif ($keyValues == "")
348
				$keyValues="";
349
		}
350
		$condition=self::getCondition($keyValues,$className);
351
		$retour=self::getAll($className, $condition." limit 1", $loadManyToOne, $loadOneToMany, $useCache);
352
		if (sizeof($retour) < 1)
353
			return null;
354
		else
355
			return \reset($retour);
356
	}
357
358
	/**
359
	 * Supprime $instance dans la base de données
360
	 * @param Classe $instance instance à supprimer
361
	 */
362
	public static function remove($instance) {
363
		$tableName=OrmUtils::getTableName(get_class($instance));
364
		$keyAndValues=OrmUtils::getKeyFieldsAndValues($instance);
365
		$sql="DELETE FROM " . $tableName . " WHERE " . SqlUtils::getWhere($keyAndValues);
366
		Logger::log("delete", $sql);
367
		$statement=self::$db->prepareStatement($sql);
368
		foreach ( $keyAndValues as $key => $value ) {
369
			self::$db->bindValueFromStatement($statement, $key, $value);
370
		}
371
		return $statement->execute();
372
	}
373
374
	/**
375
	 * Insère $instance dans la base de données
376
	 * @param object $instance instance à insérer
377
	 * @param $insertMany si vrai, sauvegarde des instances reliées à $instance par un ManyToMany
378
	 */
379
	public static function insert($instance, $insertMany=false) {
380
		$tableName=OrmUtils::getTableName(get_class($instance));
381
		$keyAndValues=Reflexion::getPropertiesAndValues($instance);
382
		$keyAndValues=array_merge($keyAndValues, OrmUtils::getManyToOneMembersAndValues($instance));
383
		$sql="INSERT INTO " . $tableName . "(" . SqlUtils::getInsertFields($keyAndValues) . ") VALUES(" . SqlUtils::getInsertFieldsValues($keyAndValues) . ")";
384
		Logger::log("insert", $sql);
385
		Logger::log("Key and values", json_encode($keyAndValues));
386
		$statement=self::$db->prepareStatement($sql);
387
		foreach ( $keyAndValues as $key => $value ) {
388
			self::$db->bindValueFromStatement($statement, $key, $value);
389
		}
390
		$result=$statement->execute();
391
		if ($result) {
392
			$accesseurId="set" . ucfirst(OrmUtils::getFirstKey(get_class($instance)));
393
			$instance->$accesseurId(self::$db->lastInserId());
394
			if ($insertMany) {
395
				self::insertOrUpdateAllManyToMany($instance);
396
			}
397
		}
398
		return $result;
399
	}
400
401
	/**
402
	 * Met à jour les membres de $instance annotés par un ManyToMany
403
	 * @param object $instance
404
	 */
405
	public static function insertOrUpdateAllManyToMany($instance) {
406
		$members=OrmUtils::getAnnotationInfo(get_class($instance), "#manyToMany");
407
		if ($members !== false) {
408
			$members=\array_keys($members);
409
			foreach ( $members as $member ) {
410
				self::insertOrUpdateManyToMany($instance, $member);
411
			}
412
		}
413
	}
414
415
	/**
416
	 * Met à jour le membre $member de $instance annoté par un ManyToMany
417
	 * @param Object $instance
418
	 * @param String $member
419
	 */
420
	public static function insertOrUpdateManyToMany($instance, $member) {
421
		$parser=new ManyToManyParser($instance, $member);
422
		if ($parser->init()) {
423
			$myField=$parser->getMyFkField();
424
			$field=$parser->getFkField();
425
			$sql="INSERT INTO `" . $parser->getJoinTable() . "`(`" . $myField . "`,`" . $field . "`) VALUES (:" . $myField . ",:" . $field . ");";
426
			$memberAccessor="get" . ucfirst($member);
427
			$memberValues=$instance->$memberAccessor();
428
			$myKey=$parser->getMyPk();
429
			$myAccessorId="get" . ucfirst($myKey);
430
			$accessorId="get" . ucfirst($parser->getPk());
431
			$id=$instance->$myAccessorId();
432
			if (!is_null($memberValues)) {
433
				self::$db->execute("DELETE FROM `" . $parser->getJoinTable() . "` WHERE `" . $myField . "`='" . $id . "'");
434
				$statement=self::$db->prepareStatement($sql);
435
				foreach ( $memberValues as $targetInstance ) {
436
					$foreignId=$targetInstance->$accessorId();
437
					$foreignInstances=self::getAll($parser->getTargetEntity(), "`" . $parser->getPk() . "`" . "='" . $foreignId . "'");
438
					if (!OrmUtils::exists($targetInstance, $parser->getPk(), $foreignInstances)) {
439
						self::insert($targetInstance, false);
440
						$foreignId=$targetInstance->$accessorId();
441
						Logger::log("InsertMany", "Insertion d'une instance de " . get_class($instance));
442
					}
443
					self::$db->bindValueFromStatement($statement, $myField, $id);
444
					self::$db->bindValueFromStatement($statement, $field, $foreignId);
445
					$statement->execute();
446
					Logger::log("InsertMany", "Insertion des valeurs dans la table association '" . $parser->getJoinTable() . "'");
447
				}
448
			}
449
		}
450
	}
451
452
	/**
453
	 * Met à jour $instance dans la base de données.
454
	 * Attention de ne pas modifier la clé primaire
455
	 * @param Classe $instance instance à modifier
456
	 * @param $updateMany Ajoute ou met à jour les membres ManyToMany
457
	 */
458
	public static function update($instance, $updateMany=false) {
459
		$tableName=OrmUtils::getTableName(get_class($instance));
460
		$ColumnskeyAndValues=Reflexion::getPropertiesAndValues($instance);
461
		$ColumnskeyAndValues=array_merge($ColumnskeyAndValues, OrmUtils::getManyToOneMembersAndValues($instance));
462
		$keyFieldsAndValues=OrmUtils::getKeyFieldsAndValues($instance);
463
		$sql="UPDATE " . $tableName . " SET " . SqlUtils::getUpdateFieldsKeyAndValues($ColumnskeyAndValues) . " WHERE " . SqlUtils::getWhere($keyFieldsAndValues);
464
		Logger::log("update", $sql);
465
		Logger::log("Key and values", json_encode($ColumnskeyAndValues));
466
		$statement=self::$db->prepareStatement($sql);
467
		foreach ( $ColumnskeyAndValues as $key => $value ) {
468
			self::$db->bindValueFromStatement($statement, $key, $value);
469
		}
470
		$result=$statement->execute();
471
		if ($result && $updateMany)
472
			self::insertOrUpdateAllManyToMany($instance);
473
		return $result;
474
	}
475
476
	/**
477
	 * Réalise la connexion à la base de données en utilisant les paramètres passés
478
	 * @param string $dbType
479
	 * @param string $dbName
480
	 * @param string $serverName
481
	 * @param string $port
482
	 * @param string $user
483
	 * @param string $password
484
	 * @param array $options
485
	 * @param boolean|string $cache
486
	 */
487
	public static function connect($dbType,$dbName, $serverName="127.0.0.1", $port="3306", $user="root", $password="", $options=[],$cache=false) {
488
		self::$db=new Database($dbType,$dbName, $serverName, $port, $user, $password, $options,$cache);
489
		try {
490
			self::$db->connect();
491
		} catch (\Exception $e) {
492
			Logger::error("DAO", $e->getMessage());
493
			throw $e;
494
		}
495
	}
496
497
	public static function isConnected(){
498
		return self::$db!==null && (self::$db instanceof Database) && self::$db->isConnected();
499
	}
500
}
501