Completed
Push — master ( fea01a...a0516a )
by Jean-Christophe
01:56
created

DAO::getManyToManyFromArray()   C

Complexity

Conditions 8
Paths 24

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $oneToManyFields does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
217
			$objects[]=$o;
218
			self::addInstanceInObjects($o);
219
		}
220
		return $objects;
221
	}
222
223
	private static function loadObjectFromRow($row,$className,$invertedJoinColumns,$oneToManyFields){
224
		$o=new $className();
225
		foreach ($row as $k=>$v){
226
			$accesseur="set".ucfirst($k);
227
			if(method_exists($o,$accesseur)){
228
				$o->$accesseur($v);
229
			}
230
			if(isset($invertedJoinColumns) && isset($invertedJoinColumns[$k])) {
231
				self::getOneManyToOne($o,$v, $invertedJoinColumns[$k]);
232
			}
233
		}
234
		if(isset($oneToManyFields)){
235
			foreach ($oneToManyFields as $k=>$annot){
236
				self::getOneToMany($o, $k,null,$annot);
237
			}
238
		}
239
		return $o;
240
	}
241
	/**
242
	 * Retourne le nombre d'objets de $className depuis la base de données respectant la condition éventuellement passée en paramètre
243
	 * @param string $className nom de la classe du model à charger
244
	 * @param string $condition Partie suivant le WHERE d'une instruction SQL
245
	 */
246
	public static function count($className,$condition=''){
247
		$tableName=OrmUtils::getTableName($className);
248
		if($condition!='')
249
			$condition=" WHERE ".$condition;
250
		return self::$db->query("SELECT COUNT(*) FROM ".$tableName.$condition)->fetchColumn();
251
	}
252
253
	/**
254
	 * Retourne une instance de $className depuis la base de données, à  partir des valeurs $keyValues de la clé primaire
255
	 * @param String $className nom de la classe du model à charger
256
	 * @param Array|string $keyValues valeurs des clés primaires ou condition
257
	 */
258
	public static function getOne($className,$keyValues,$loadManyToOne=true,$loadOneToMany=false){
259
		if(!is_array($keyValues)){
260
			if(strrpos($keyValues,"=")===false){
261
				$keyValues="`".OrmUtils::getFirstKey($className)."`='".$keyValues."'";
262
			}elseif ($keyValues=="")
263
				$keyValues="";
264
		}
265
		$condition=self::getCondition($keyValues);
266
		$retour=self::getInstanceInObjects($className,$condition);
267
		if(!isset($retour)){
268
			$retour=self::getAll($className,$condition,$loadManyToOne,$loadOneToMany);
269
			if(sizeof($retour)<1)
270
				return null;
271
			else
272
				return $retour[0];
273
		}
274
		return $retour;
275
276
	}
277
278
	/**
279
	 * Supprime $instance dans la base de données
280
	 * @param Classe $instance instance à supprimer
281
	 */
282
	public static function remove($instance){
283
		$tableName=OrmUtils::getTableName(get_class($instance));
284
		$keyAndValues=OrmUtils::getKeyFieldsAndValues($instance);
285
		$sql="DELETE FROM ".$tableName." WHERE ".SqlUtils::getWhere($keyAndValues);
286
		Logger::log("delete", $sql);
287
		$statement=self::$db->prepareStatement($sql);
288
		foreach ($keyAndValues as $key=>$value){
289
			self::$db->bindValueFromStatement($statement,$key,$value);
290
		}
291
		return $statement->execute();
292
	}
293
294
	/**
295
	 * Insère $instance dans la base de données
296
	 * @param object $instance instance à insérer
297
	 * @param $insertMany si vrai, sauvegarde des instances reliées à $instance par un ManyToMany
298
	 */
299
	public static function insert($instance,$insertMany=false){
300
		$tableName=OrmUtils::getTableName(get_class($instance));
301
		$keyAndValues=Reflexion::getPropertiesAndValues($instance);
302
		$keyAndValues=array_merge($keyAndValues, OrmUtils::getManyToOneMembersAndValues($instance));
303
		$sql="INSERT INTO ".$tableName."(".SqlUtils::getInsertFields($keyAndValues).") VALUES(".SqlUtils::getInsertFieldsValues($keyAndValues).")";
304
		Logger::log("insert", $sql);
305
		Logger::log("Key and values", json_encode($keyAndValues));
306
		$statement=self::$db->prepareStatement($sql);
307
		foreach ($keyAndValues as $key=>$value){
308
				self::$db->bindValueFromStatement($statement,$key,$value);
309
		}
310
		$result=$statement->execute();
311
		if($result){
312
			$accesseurId="set".ucfirst(OrmUtils::getFirstKey(get_class($instance)));
313
			$instance->$accesseurId(self::$db->lastInserId());
314
			if($insertMany){
315
				self::insertOrUpdateAllManyToMany($instance);
316
			}
317
		}
318
		return $result;
319
	}
320
321
	/**
322
	 * Met à jour les membres de $instance annotés par un ManyToMany
323
	 * @param object $instance
324
	 */
325
	public static function insertOrUpdateAllManyToMany($instance){
326
		$members=OrmUtils::getAnnotationInfo(get_class($instance), "#manyToMany");
327
		if($members!==false){
328
			$members=\array_keys($members);
329
			foreach ($members as $member){
330
				self::insertOrUpdateManyToMany($instance, $member);
331
			}
332
		}
333
	}
334
335
	/**
336
	 * Met à jour le membre $member de $instance annoté par un ManyToMany
337
	 * @param Object $instance
338
	 * @param String $member
339
	 */
340
	public static function insertOrUpdateManyToMany($instance,$member){
341
		$parser=new ManyToManyParser($instance, $member);
342
		if($parser->init()){
343
			$myField=$parser->getMyFkField();
344
			$field=$parser->getFkField();
345
			$sql="INSERT INTO `".$parser->getJoinTable()."`(`".$myField."`,`".$field."`) VALUES (:".$myField.",:".$field.");";
346
			$memberAccessor="get".ucfirst($member);
347
			$memberValues=$instance->$memberAccessor();
348
			$myKey=$parser->getMyPk();
349
			$myAccessorId="get".ucfirst($myKey);
350
			$accessorId="get".ucfirst($parser->getPk());
351
			$id=$instance->$myAccessorId();
352
			if(!is_null($memberValues)){
353
				self::$db->execute("DELETE FROM `".$parser->getJoinTable()."` WHERE `".$myField."`='".$id."'");
354
				$statement=self::$db->prepareStatement($sql);
355
				foreach ($memberValues as $targetInstance){
356
					$foreignId=$targetInstance->$accessorId();
357
					$foreignInstances=self::getAll($parser->getTargetEntity(), "`".$parser->getPk()."`"."='".$foreignId."'");
358
					if(!OrmUtils::exists($targetInstance, $parser->getPk(), $foreignInstances)){
359
						self::insert($targetInstance,false);
360
						$foreignId=$targetInstance->$accessorId();
361
						Logger::log("InsertMany", "Insertion d'une instance de ".get_class($instance));
362
					}
363
					self::$db->bindValueFromStatement($statement,$myField,$id);
364
					self::$db->bindValueFromStatement($statement,$field,$foreignId);
365
					$statement->execute();
366
					Logger::log("InsertMany", "Insertion des valeurs dans la table association '".$parser->getJoinTable()."'");
367
				}
368
			}
369
		}
370
	}
371
	/**
372
	 * Met à jour $instance dans la base de données.
373
	 * Attention de ne pas modifier la clé primaire
374
	 * @param Classe $instance instance à modifier
375
	 * @param $updateMany Ajoute ou met à jour les membres ManyToMany
376
	 */
377
	public static function update($instance,$updateMany=false){
378
		$tableName=OrmUtils::getTableName(get_class($instance));
379
		$ColumnskeyAndValues=Reflexion::getPropertiesAndValues($instance);
380
		$ColumnskeyAndValues=array_merge($ColumnskeyAndValues, OrmUtils::getManyToOneMembersAndValues($instance));
381
		$keyFieldsAndValues=OrmUtils::getKeyFieldsAndValues($instance);
382
		$sql="UPDATE ".$tableName." SET ".SqlUtils::getUpdateFieldsKeyAndValues($ColumnskeyAndValues)." WHERE ".SqlUtils::getWhere($keyFieldsAndValues);
383
		Logger::log("update", $sql);
384
		Logger::log("Key and values", json_encode($ColumnskeyAndValues));
385
		$statement=self::$db->prepareStatement($sql);
386
		foreach ($ColumnskeyAndValues as $key=>$value){
387
				self::$db->bindValueFromStatement($statement,$key,$value);
388
		}
389
		$result= $statement->execute();
390
		if($result && $updateMany)
391
			self::insertOrUpdateAllManyToMany($instance);
392
		return $result;
393
	}
394
395
	/**
396
	 * Réalise la connexion à la base de données en utilisant les paramètres passés
397
	 * @param string $dbName
398
	 * @param string $serverName
399
	 * @param string $port
400
	 * @param string $user
401
	 * @param string $password
402
	 */
403
	public static function connect($dbName,$serverName="127.0.0.1",$port="3306",$user="root",$password=""){
404
		self::$db=new Database($dbName,$serverName,$port,$user,$password);
405
		self::$db->connect();
406
	}
407
}
408