Completed
Push — master ( 5f0535...74f4b9 )
by Jean-Christophe
01:50
created

DAO::getOne()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
dl 0
loc 12
rs 9.4285
c 3
b 0
f 0
cc 3
eloc 10
nc 4
nop 5
1
<?php
2
3
namespace Ubiquity\orm;
4
5
use Ubiquity\db\Database;
6
use Ubiquity\log\Logger;
7
use Ubiquity\orm\parser\ManyToManyParser;
8
use Ubiquity\db\SqlUtils;
9
use Ubiquity\orm\parser\Reflexion;
10
use Ubiquity\orm\traits\DAOUpdatesTrait;
11
12
/**
13
 * Gateway class between database and object model
14
 * @author jc
15
 * @version 1.0.0.6
16
 * @package orm
17
 */
18
class DAO {
19
	use DAOUpdatesTrait;
20
	/**
21
	 * @var Database
22
	 */
23
	public static $db;
24
25
	/**
26
	 * Loads member associated with $instance by a ManyToOne type relationship
27
	 * @param object $instance
28
	 * @param string $member
29
	 * @param boolean $useCache
30
	 */
31
	public static function getManyToOne($instance, $member, $useCache=NULL) {
32
		$fieldAnnot=OrmUtils::getMemberJoinColumns($instance, $member);
33
		if($fieldAnnot!==null){
34
			$field=$fieldAnnot[0];
35
			$value=Reflexion::getMemberValue($instance, $field);
36
			$annotationArray=$fieldAnnot[1];
37
			$member=$annotationArray["member"];
38
			$key=OrmUtils::getFirstKey($annotationArray["className"]);
39
			$kv=array ($key => $value );
40
			$obj=self::getOne($annotationArray["className"], $kv, false, false, $useCache);
41
			if ($obj !== null) {
42
				Logger::log("getManyToOne", "Chargement de " . $member . " pour l'objet " . \get_class($instance));
43
				$accesseur="set" . ucfirst($member);
44
				if (method_exists($instance, $accesseur)) {
45
					$instance->$accesseur($obj);
46
					$instance->_rest[$member]=$obj->_rest;
47
					return;
48
				}
49
			}
50
		}
51
	}
52
53
	private static function _getOneToManyFromArray(&$ret, $array, $fkv, $mappedBy) {
54
		$elementAccessor="get" . ucfirst($mappedBy);
55
		foreach ( $array as $element ) {
56
			$elementRef=$element->$elementAccessor();
57
			if (!is_null($elementRef)) {
58
				$idElementRef=OrmUtils::getFirstKeyValue($elementRef);
59
				if ($idElementRef == $fkv)
60
					$ret[]=$element;
61
			}
62
		}
63
	}
64
65
	/**
66
	 * Assign / load the child records in the $member member of $instance.
67
	 * @param object $instance
68
	 * @param string $member Member on which a oneToMany annotation must be present
69
	 * @param boolean $useCache
70
	 * @param array $annot used internally
71
	 */
72
	public static function getOneToMany($instance, $member, $useCache=NULL, $annot=null) {
73
		$ret=array ();
74
		$class=get_class($instance);
75
		if (!isset($annot))
76
			$annot=OrmUtils::getAnnotationInfoMember($class, "#oneToMany", $member);
77
			if ($annot !== false) {
78
				$fkAnnot=OrmUtils::getAnnotationInfoMember($annot["className"], "#joinColumn", $annot["mappedBy"]);
79
				if ($fkAnnot !== false) {
80
					$fkv=OrmUtils::getFirstKeyValue($instance);
81
					$ret=self::getAll($annot["className"], $fkAnnot["name"] . "='" . $fkv . "'", true, false, $useCache);
82
					self::setToMember($member, $instance, $ret, $class, "getOneToMany");
83
				}
84
			}
85
			return $ret;
86
	}
87
88
	/**
89
	 * @param object $instance
90
	 * @param string $member
91
	 * @param array $array
92
	 * @param string $mappedBy
93
	 */
94
	public static function affectsOneToManyFromArray($instance, $member, $array=null, $mappedBy=null) {
95
		$ret=array ();
96
		$class=get_class($instance);
97
		if (!isset($mappedBy)){
98
			$annot=OrmUtils::getAnnotationInfoMember($class, "#oneToMany", $member);
99
			$mappedBy=$annot["mappedBy"];
100
		}
101
		if ($mappedBy !== false) {
102
				$fkv=OrmUtils::getFirstKeyValue($instance);
103
				self::_getOneToManyFromArray($ret, $array, $fkv, $mappedBy);
104
				self::setToMember($member, $instance, $ret, $class, "getOneToMany");
105
		}
106
		return $ret;
107
	}
108
109
	private static function setToMember($member, $instance, $value, $class, $part) {
110
		$accessor="set" . ucfirst($member);
111
		if (method_exists($instance, $accessor)) {
112
			Logger::log($part, "Affectation de " . $member . " pour l'objet " . $class);
113
			$instance->$accessor($value);
114
			$instance->_rest[$member]=$value;
115
		} else {
116
			Logger::warn($part, "L'accesseur " . $accessor . " est manquant pour " . $class);
117
		}
118
	}
119
120
	/**
121
	 * Assigns / loads the child records in the $member member of $instance.
122
	 * If $ array is null, the records are loaded from the database
123
	 * @param object $instance
124
	 * @param string $member Member on which a ManyToMany annotation must be present
125
	 * @param array $array optional parameter containing the list of possible child records
126
	 * @param boolean $useCache
127
	 */
128
	public static function getManyToMany($instance, $member,$array=null,$useCache=NULL){
129
		$ret=array ();
130
		$class=get_class($instance);
131
		$parser=new ManyToManyParser($instance, $member);
132
		if ($parser->init()) {
133
			if (is_null($array)) {
134
				$accessor="get" . ucfirst($parser->getMyPk());
135
				$condition=" INNER JOIN `" . $parser->getJoinTable() . "` on `".$parser->getJoinTable()."`.`".$parser->getFkField()."`=`".$parser->getTargetEntityTable()."`.`".$parser->getPk()."` WHERE `".$parser->getJoinTable()."`.`". $parser->getMyFkField() . "`='" . $instance->$accessor() . "'";
136
				$ret=self::getAll($parser->getTargetEntityClass(),$condition,true,false,$useCache);
137
			}else{
138
				self::getManyToManyFromArray($ret, $instance, $array, $class, $parser);
139
			}
140
			self::setToMember($member, $instance, $ret, $class, "getManyToMany");
141
		}
142
		return $ret;
143
	}
144
145
	/**
146
	 * @param object $instance
147
	 * @param array $array
148
	 * @param boolean $useCache
149
	 */
150
	public static function affectsManyToManys($instance,$array=NULL,$useCache=NULL){
151
		$metaDatas=OrmUtils::getModelMetadata(\get_class($instance));
152
		$manyToManyFields=$metaDatas["#manyToMany"];
153
		if(\sizeof($manyToManyFields)>0){
154
			foreach ($manyToManyFields as $member){
155
				self::getManyToMany($instance, $member,$array,$useCache);
156
			}
157
		}
158
	}
159
160
	private static function getManyToManyFromArray(&$ret, $instance, $array, $class, $parser) {
161
		$continue=true;
162
		$accessorToMember="get" . ucfirst($parser->getInversedBy());
163
		$myPkAccessor="get" . ucfirst($parser->getMyPk());
164
165
		if (!method_exists($instance, $myPkAccessor)) {
166
			Logger::warn("ManyToMany", "L'accesseur au membre clé primaire " . $myPkAccessor . " est manquant pour " . $class);
167
		}
168
		if (count($array) > 0)
169
			$continue=method_exists($array[0], $accessorToMember);
170
		if ($continue) {
171
			foreach ( $array as $targetEntityInstance ) {
172
				$instances=$targetEntityInstance->$accessorToMember();
173
				if (is_array($instances)) {
174
					foreach ( $instances as $inst ) {
175
						if ($inst->$myPkAccessor() == $instance->$myPkAccessor())
176
							array_push($ret, $targetEntityInstance);
177
					}
178
				}
179
			}
180
		} else {
181
			Logger::warn("ManyToMany", "L'accesseur au membre " . $parser->getInversedBy() . " est manquant pour " . $parser->getTargetEntity());
182
		}
183
	}
184
185
	/**
186
	 * Returns an array of $className objects from the database
187
	 * @param string $className class name of the model to load
188
	 * @param string $condition Part following the WHERE of an SQL statement
189
	 * @param boolean $loadManyToOne if true, charges associate members with manyToOne association
190
	 * @param boolean $loadOneToMany if true, charges associate members with oneToMany association
191
	 * @param boolean $useCache use the active cache if true
192
	 * @return array
193
	 */
194
	public static function getAll($className, $condition='', $loadManyToOne=true, $loadOneToMany=false,$useCache=NULL) {
195
		$objects=array ();
196
		$invertedJoinColumns=null;
197
		$oneToManyFields=null;
198
		$metaDatas=OrmUtils::getModelMetadata($className);
199
		$tableName=$metaDatas["#tableName"];
200
		if ($loadManyToOne && isset($metaDatas["#invertedJoinColumn"]))
201
			$invertedJoinColumns=$metaDatas["#invertedJoinColumn"];
202
		if ($loadOneToMany && isset($metaDatas["#oneToMany"])) {
203
			$oneToManyFields=$metaDatas["#oneToMany"];
204
		}
205
		$condition=SqlUtils::checkWhere($condition);
206
		$members=\array_diff($metaDatas["#fieldNames"],$metaDatas["#notSerializable"]);
207
		$query=self::$db->prepareAndExecute($tableName, $condition,$members,$useCache);
208
		Logger::log("getAll", "SELECT * FROM " . $tableName . $condition);
209
		$oneToManyQueries=[];
210
		$manyToOneQueries=[];
211
212
		foreach ( $query as $row ) {
213
			$object=self::loadObjectFromRow($row, $className, $invertedJoinColumns, $oneToManyFields,$members, $oneToManyQueries,$manyToOneQueries);
214
			$key=OrmUtils::getFirstKeyValue($object);
215
			$objects[$key]=$object;
216
		}
217
218
		if($loadManyToOne && \sizeof($manyToOneQueries)>0){
219
			self::_affectsObjectsFromArray($manyToOneQueries, $objects, function($object,$member,$manyToOneObjects,$fkField){
220
				self::affectsManyToOneFromArray($object,$member,$manyToOneObjects,$fkField);
221
			});
222
		}
223
224
		if($loadOneToMany && \sizeof($oneToManyQueries)>0){
225
			self::_affectsObjectsFromArray($oneToManyQueries, $objects, function($object,$member,$relationObjects,$fkField){
226
				self::affectsOneToManyFromArray($object,$member,$relationObjects,$fkField);
227
			});
228
		}
229
		return $objects;
230
	}
231
	
232
	public static function paginate($className,$page=1,$rowsPerPage=20,$condition=null){
233
		if(!isset($condition)){
234
			$condition="1=1";
235
		}
236
		return self::getAll($className,$condition." LIMIT ".$rowsPerPage." OFFSET ".(($page-1)*$rowsPerPage));
237
	}
238
	
239
	public static function getRownum($className,$ids){
240
		$tableName=OrmUtils::getTableName($className);
241
		self::parseKey($ids,$className);
242
		$condition=SqlUtils::getCondition($ids,$className);
243
		$keys=implode(",", OrmUtils::getKeyFields($className));
244
		return self::$db->queryColumn("SELECT num FROM (SELECT *, @rownum:=@rownum + 1 AS num FROM {$tableName}, (SELECT @rownum:=0) r ORDER BY {$keys}) d WHERE ".$condition);
245
	}
246
247
	private static function _affectsObjectsFromArray($queries,$objects,$affectsCallback,$useCache=NULL){
248
		foreach ($queries as $key=>$conditions){
249
			list($class,$member,$fkField)=\explode("|", $key);
250
			$condition=\implode(" OR ", $conditions);
251
			$relationObjects=self::getAll($class,$condition,true,false,$useCache);
252
			foreach ($objects as $object){
253
				$affectsCallback($object, $member,$relationObjects,$fkField);
254
			}
255
		}
256
	}
257
258
	private static function affectsManyToOneFromArray($object,$member,$manyToOneObjects,$fkField){
259
		$class=\get_class($object);
260
		if(isset($object->$fkField)){
261
			$value=$manyToOneObjects[$object->$fkField];
262
			self::setToMember($member, $object, $value, $class, "getManyToOne");
263
		}
264
	}
265
266
	/**
267
	 * @param array $row
268
	 * @param string $className
269
	 * @param array $invertedJoinColumns
270
	 * @param array $oneToManyFields
271
	 * @param array $members
272
	 * @param array $oneToManyQueries
273
	 * @param array $manyToOneQueries
274
	 * @return object
275
	 */
276
	private static function loadObjectFromRow($row, $className, $invertedJoinColumns, $oneToManyFields, $members,&$oneToManyQueries,&$manyToOneQueries) {
277
		$o=new $className();
278
		foreach ( $row as $k => $v ) {
279
			if(($field=\array_search($k, $members))!==false){
280
				$accesseur="set" . ucfirst($field);
281
				if (method_exists($o, $accesseur)) {
282
					$o->$accesseur($v);
283
				}
284
			}
285
			$o->_rest[$k]=$v;
286
			if (isset($invertedJoinColumns) && isset($invertedJoinColumns[$k])) {
287
				$fk="_".$k;
288
				$o->$fk=$v;
289
				self::prepareManyToOne($manyToOneQueries,$v, $fk,$invertedJoinColumns[$k]);
290
			}
291
		}
292
		if (isset($oneToManyFields)) {
293
			foreach ( $oneToManyFields as $k => $annot ) {
294
				self::prepareOneToMany($oneToManyQueries,$o, $k, $annot);
295
			}
296
		}
297
		return $o;
298
	}
299
300
301
	/**
302
	 * Prepares members associated with $instance with a oneToMany type relationship
303
	 * @param $ret array of sql conditions
304
	 * @param object $instance
305
	 * @param string $member Member on which a OneToMany annotation must be present
306
	 * @param array $annot used internally
307
	 */
308
	private static function prepareOneToMany(&$ret,$instance, $member, $annot=null) {
309
		$class=get_class($instance);
310
		if (!isset($annot))
311
			$annot=OrmUtils::getAnnotationInfoMember($class, "#oneToMany", $member);
312
			if ($annot !== false) {
313
				$fkAnnot=OrmUtils::getAnnotationInfoMember($annot["className"], "#joinColumn", $annot["mappedBy"]);
314
				if ($fkAnnot !== false) {
315
					$fkv=OrmUtils::getFirstKeyValue($instance);
316
					$key=$annot["className"]."|".$member."|".$annot["mappedBy"];
317
					if(!isset($ret[$key])){
318
						$ret[$key]=[];
319
					}
320
					$ret[$key][$fkv]=$fkAnnot["name"] . "='" . $fkv . "'";
321
				}
322
			}
323
	}
324
325
	/**
326
	 * Prepares members associated with $instance with a manyToOne type relationship
327
	 * @param $ret array of sql conditions
328
	 * @param mixed $value
329
	 * @param string $fkField
330
	 * @param array $annotationArray
331
	 */
332
	private static function prepareManyToOne(&$ret, $value, $fkField,$annotationArray) {
333
		$member=$annotationArray["member"];
334
		$fk=OrmUtils::getFirstKey($annotationArray["className"]);
335
		$key=$annotationArray["className"]."|".$member."|".$fkField;
336
		if(!isset($ret[$key])){
337
			$ret[$key]=[];
338
		}
339
		$ret[$key][$value]=$fk . "='" . $value . "'";
340
	}
341
342
	/**
343
	 * Returns the number of objects of $className from the database respecting the condition possibly passed as parameter
344
	 * @param string $className complete classname of the model to load
345
	 * @param string $condition Part following the WHERE of an SQL statement
346
	 * @return int count of objects
347
	 */
348
	public static function count($className, $condition='') {
349
		$tableName=OrmUtils::getTableName($className);
350
		if ($condition != '')
351
			$condition=" WHERE " . $condition;
352
		return self::$db->query("SELECT COUNT(*) FROM " . $tableName . $condition)->fetchColumn();
353
	}
354
355
	/**
356
	 * Returns an instance of $className from the database, from $keyvalues values of the primary key
357
	 * @param String $className complete classname of the model to load
358
	 * @param Array|string $keyValues primary key values or condition
359
	 * @param boolean $loadManyToOne if true, charges associate members with manyToOne association
360
	 * @param boolean $loadOneToMany if true, charges associate members with oneToMany association
361
	 * @param boolean $useCache use cache if true
362
	 * @return object the instance loaded or null if not found
363
	 */
364
	public static function getOne($className, $keyValues, $loadManyToOne=true, $loadOneToMany=false, $useCache=NULL) {
365
		self::parseKey($keyValues,$className);
366
		$condition=SqlUtils::getCondition($keyValues,$className);
367
		$limit="";
368
		if(\stripos($condition, " limit ")===false)
369
			$limit=" limit 1";
370
		$retour=self::getAll($className, $condition.$limit, $loadManyToOne, $loadOneToMany,$useCache);
371
		if (sizeof($retour) < 1){
372
			return null;
373
		}
374
		return \reset($retour);
375
	}
376
	
377
	private static function parseKey(&$keyValues,$className){
378
		if (!is_array($keyValues)) {
379
			if (strrpos($keyValues, "=") === false && strrpos($keyValues, ">") === false && strrpos($keyValues, "<") === false) {
380
				$keyValues="`" . OrmUtils::getFirstKey($className) . "`='" . $keyValues . "'";
381
			}
382
		}
383
	}
384
385
	/**
386
	 * Establishes the connection to the database using the past parameters
387
	 * @param string $dbType
388
	 * @param string $dbName
389
	 * @param string $serverName
390
	 * @param string $port
391
	 * @param string $user
392
	 * @param string $password
393
	 * @param array $options
394
	 * @param boolean $cache
395
	 */
396
	public static function connect($dbType,$dbName, $serverName="127.0.0.1", $port="3306", $user="root", $password="", $options=[],$cache=false) {
397
		self::$db=new Database($dbType,$dbName, $serverName, $port, $user, $password, $options,$cache);
398
		try {
399
			self::$db->connect();
400
		} catch (\Exception $e) {
401
			Logger::error("DAO", $e->getMessage());
402
			throw $e;
403
		}
404
	}
405
406
	/**
407
	 * Returns true if the connection to the database is estabished
408
	 * @return boolean
409
	 */
410
	public static function isConnected(){
411
		return self::$db!==null && (self::$db instanceof Database) && self::$db->isConnected();
412
	}
413
}
414