- First draft of current WIP
This commit is contained in:
		
						commit
						b73d046e0a
					
				
							
								
								
									
										18
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| { | ||||
|     "name": "mcnd/orm", | ||||
|     "description": "An hybrid of Active Record and Data Mapper pattern allowing fdirect queries.", | ||||
|     "type": "library", | ||||
|     "license": "MIT", | ||||
|     "authors": [ | ||||
|         { | ||||
|             "name": "Dave Mc Nicoll", | ||||
|             "email": "mcndave@gmail.com" | ||||
|         } | ||||
|     ], | ||||
|     "require": {}, | ||||
|     "autoload": { | ||||
|         "psr-4": { | ||||
|             "Ulmus\\": "src/" | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/Adapter/AdapterInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/Adapter/AdapterInterface.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Adapter; | ||||
| 
 | ||||
| use Ulmus\Common\PdoObject; | ||||
| 
 | ||||
| interface AdapterInterface { | ||||
|     public function connect() : PdoObject; | ||||
|     public function buildDataSourceName() : string; | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/Adapter/MariaDB.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/Adapter/MariaDB.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Adapter; | ||||
| 
 | ||||
| class MariaDB extends MySQL { | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										77
									
								
								src/Adapter/MySQL.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/Adapter/MySQL.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Adapter; | ||||
| 
 | ||||
| use Ulmus\Common\PdoObject; | ||||
| 
 | ||||
| class MySQL implements AdapterInterface { | ||||
| 
 | ||||
|     public string $hostname; | ||||
|     public string $database; | ||||
|     public string $username; | ||||
|     public string $password; | ||||
|     public string $charset; | ||||
|     public int $port; | ||||
| 
 | ||||
|     public function __construct( | ||||
|         ?string $hostname = null, | ||||
|         ?string $database = null, | ||||
|         ?string $username = null, | ||||
|         ?string $password = null, | ||||
|         ?int $port = null, | ||||
|         ?string $charset = null | ||||
|     ) { | ||||
|         if ($hostname) { | ||||
|             $this->hostname = $hostname; | ||||
|         } | ||||
| 
 | ||||
|         if ($database) { | ||||
|             $this->database = $database; | ||||
|         } | ||||
| 
 | ||||
|         if ($port) { | ||||
|             $this->port = $port; | ||||
|         } | ||||
| 
 | ||||
|         if ($username) { | ||||
|             $this->username = $username; | ||||
|         } | ||||
| 
 | ||||
|         if ($password) { | ||||
|             $this->password = $password; | ||||
|         } | ||||
| 
 | ||||
|         if ($charset) { | ||||
|             $this->charset = $charset; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function connect() : PdoObject | ||||
|     { | ||||
|         try { | ||||
|             $obj = new PdoObject($this->buildDataSourceName(), $this->username, $this->password); | ||||
|         } | ||||
|         catch(PDOException $ex){ | ||||
|             throw $ex; | ||||
|         } | ||||
| 
 | ||||
|         return $obj; | ||||
|     } | ||||
| 
 | ||||
|     public function buildDataSourceName() : string | ||||
|     { | ||||
|         $parts[] = "host={$this->hostname}"; | ||||
|         $parts[] = "dbname={$this->database}"; | ||||
|         $parts[] = "port={$this->port}"; | ||||
| 
 | ||||
|         if ( $this->socket ?? false ) { | ||||
|             $parts[] = "socket={$this->socket}"; | ||||
|         } | ||||
| 
 | ||||
|         if ( $this->charset ?? false ) { | ||||
|             $parts[] = "charset={$this->charset}"; | ||||
|         } | ||||
| 
 | ||||
|         return "mysql:" . implode(';', $parts); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										5
									
								
								src/Annotation/Annotation.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/Annotation/Annotation.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation; | ||||
| 
 | ||||
| interface Annotation {} | ||||
							
								
								
									
										103
									
								
								src/Annotation/AnnotationReader.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/Annotation/AnnotationReader.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation; | ||||
| 
 | ||||
| use Reflector, ReflectionClass, ReflectionProperty, ReflectionMethod; | ||||
| 
 | ||||
| /** | ||||
|  * This class exists while waiting for the official RFC [ https://wiki.php.net/rfc/annotations_v2 ] | ||||
|  */ | ||||
| class AnnotationReader | ||||
| { | ||||
|     const PHP_TYPES = [ "string", "int", "float", "object", "double", "closure", ]; | ||||
| 
 | ||||
|     public string $class; | ||||
| 
 | ||||
|     public function __construct($class) { | ||||
|         $this->class = $class; | ||||
|     } | ||||
| 
 | ||||
|     public static function fromClass($class) : self | ||||
|     { | ||||
|         return new static($class); | ||||
|     } | ||||
| 
 | ||||
|     public function getProperty(ReflectionProperty $property) | ||||
|     { | ||||
|         return $this->parseDocComment($property); | ||||
|     } | ||||
| 
 | ||||
|     public function getClass(ReflectionClass $class) | ||||
|     { | ||||
|         return $this->parseDocComment($class); | ||||
|     } | ||||
| 
 | ||||
|     public function getMethod(ReflectionMethod $method) | ||||
|     { | ||||
|         return $this->parseDocComment($method); | ||||
|     } | ||||
| 
 | ||||
|     protected function parseDocComment(Reflector $reflect) | ||||
|     { | ||||
|         $namespace = $this->getObjectNamespace($reflect); | ||||
|         $tags = []; | ||||
| 
 | ||||
|         foreach(preg_split("/\r\n|\n|\r/", $reflect->getDocComment()) as $line) { | ||||
|             $line = ltrim($line, "* \t\/"); | ||||
|             $line = rtrim($line, "\t "); | ||||
| 
 | ||||
|             if ( substr($line, 0, 1) === '@' ) { | ||||
|                 $line = ltrim($line, '@'); | ||||
| 
 | ||||
|                 $open = strpos($line, "("); | ||||
|                 $close = strrpos($line, ")"); | ||||
| 
 | ||||
|                 if ( ! in_array(false, [ $open, $close ], true) && ( ++$open !== $close ) ) { | ||||
|                     $arguments = substr($line, $open, $close - $open); | ||||
| 
 | ||||
|                     try { | ||||
|                         $tags[] = [ | ||||
|                             'tag' => substr($line, 0, $open - 1), | ||||
|                             'arguments' => eval("namespace $namespace; return [ $arguments ];"), | ||||
|                         ]; | ||||
|                     } | ||||
|                     catch(\Throwable $error) { | ||||
|                         throw new \InvalidArgumentException("An error occured while parsing annotation from '" . $this->getObjectName($reflect) . "' : @$line -- " . $error->getMessage()); | ||||
|                     } | ||||
|                 } | ||||
|                 else { | ||||
|                     $tags[] = [ | ||||
|                         'tag' => $line, | ||||
|                         'arguments' => [], | ||||
|                     ]; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $tags; | ||||
|     } | ||||
| 
 | ||||
|     protected function getObjectName(Reflector $reflect) : string | ||||
|     { | ||||
|         switch(true) { | ||||
|             case $reflect instanceof ReflectionMethod : | ||||
|             case $reflect instanceof ReflectionProperty : | ||||
|                 return $reflect->class . "::" . $reflect->name; | ||||
| 
 | ||||
|             case $reflect instanceof ReflectionClass : | ||||
|                 return $reflect->name; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected function getObjectNamespace(Reflector $reflect) : string | ||||
|     { | ||||
|         switch(true) { | ||||
|             case $reflect instanceof ReflectionMethod : | ||||
|             case $reflect instanceof ReflectionProperty : | ||||
|                 return $reflect->getDeclaringClass()->getNamespaceName(); | ||||
| 
 | ||||
|             case $reflect instanceof ReflectionClass : | ||||
|                 return $reflect->getNamespaceName(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/Annotation/Classes/Collation.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/Annotation/Classes/Collation.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation\Classes; | ||||
| 
 | ||||
| class Collation implements \Ulmus\Annotation\Annotation { | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/Annotation/Classes/Method.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/Annotation/Classes/Method.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation\Classes; | ||||
| 
 | ||||
| class Function implements \Ulmus\Annotation\Annotation { | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/Annotation/Classes/Table.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Annotation/Classes/Table.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation\Classes; | ||||
| 
 | ||||
| class Table implements \Ulmus\Annotation\Annotation { | ||||
| 
 | ||||
|     public string $name; | ||||
| 
 | ||||
|     public function __construct($name = null) | ||||
|     { | ||||
|         if ( $name !== null ) { | ||||
|             $this->name = $name; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/Annotation/Property/Field.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/Annotation/Property/Field.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation\Property; | ||||
| 
 | ||||
| class Field implements \Ulmus\Annotation\Annotation { | ||||
| 
 | ||||
|     public string $type; | ||||
| 
 | ||||
|     public string $name; | ||||
| 
 | ||||
|     public int $length; | ||||
| 
 | ||||
|     public array $attributes = []; | ||||
| 
 | ||||
|     public bool $nullable = false; | ||||
| 
 | ||||
|     public function __construct(string $type = null, int $length = null) | ||||
|     { | ||||
|         switch(true) { | ||||
|             case $type !== null: | ||||
|                 $this->type = $type; | ||||
|             break; | ||||
| 
 | ||||
|             case $length !== null: | ||||
|                 $this->length = $length; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										13
									
								
								src/Annotation/Property/Field/CreatedAt.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Annotation/Property/Field/CreatedAt.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation\Property\Field; | ||||
| 
 | ||||
| class CreatedAt extends \Ulmus\Annotation\Property\Field { | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->nullable = false; | ||||
|         $this->type = "timestamp"; | ||||
|         $this->attributes['default'] = "CURRENT_TIMESTAMP"; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/Annotation/Property/Field/ForeignKey.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/Annotation/Property/Field/ForeignKey.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation\Property\Field; | ||||
| 
 | ||||
| /** | ||||
|  * Since we need consistancy between the declaration of our ID and FK fields, it | ||||
|  * was decided to extend the Id class instead of Field for this case. | ||||
|  */ | ||||
| class ForeignKey extends Id { | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
|         parent::__construct(); | ||||
| 
 | ||||
|         unset($this->nullable); | ||||
|         $this->attributes['primary_key'] = false; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/Annotation/Property/Field/Id.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Annotation/Property/Field/Id.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation\Property\Field; | ||||
| 
 | ||||
| class Id extends \Ulmus\Annotation\Property\Field { | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->nullable = false; | ||||
|         $this->type = "int"; | ||||
|         $this->attributes['unsigned'] = true; | ||||
|         $this->attributes['primary_key'] = true; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/Annotation/Property/Field/UpdatedAt.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Annotation/Property/Field/UpdatedAt.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation\Property\Field; | ||||
| 
 | ||||
| class UpdatedAt extends \Ulmus\Annotation\Property\Field { | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->nullable = true; | ||||
|         $this->type = "timestamp"; | ||||
|         $this->attributes['update'] = "CURRENT_TIMESTAMP"; | ||||
|         $this->attributes['default'] = null; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/Annotation/Property/GroupBy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Annotation/Property/GroupBy.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation\Property; | ||||
| 
 | ||||
| class GroupBy implements \Ulmus\Annotation\Annotation { | ||||
| 
 | ||||
|     public array $fields = []; | ||||
| 
 | ||||
|     public function __construct(...$field) | ||||
|     { | ||||
|         if ( $field ) { | ||||
|             $this->fields = $field; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/Annotation/Property/OrderBy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/Annotation/Property/OrderBy.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation\Property; | ||||
| 
 | ||||
| class OrderBy implements \Ulmus\Annotation\Annotation { | ||||
| 
 | ||||
|     public string $field; | ||||
| 
 | ||||
|     public string $order = "ASC"; | ||||
| 
 | ||||
|     public function __construct(string $field = null, string $order = null) | ||||
|     { | ||||
|         switch(true) { | ||||
|             case $field !== null: | ||||
|                 $this->field = $field; | ||||
|             break; | ||||
| 
 | ||||
|             case $order !== null: | ||||
|                 $this->order = $order; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/Annotation/Property/Relation.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/Annotation/Property/Relation.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation\Property; | ||||
| 
 | ||||
| class Relation implements \Ulmus\Annotation\Annotation { | ||||
| 
 | ||||
|     public string $type; | ||||
| 
 | ||||
|     public string $key; | ||||
| 
 | ||||
|     public string $foreignKey; | ||||
| 
 | ||||
|     public array $foreignKeys; | ||||
| 
 | ||||
|     public string $bridge; | ||||
| 
 | ||||
|     public string $bridgeKey; | ||||
| 
 | ||||
|     public string $bridgeForeignKey; | ||||
| 
 | ||||
|     public function __construct(string $type = null) | ||||
|     { | ||||
|         switch(true) { | ||||
|             case $type !== null: | ||||
|                 $this->type = $type; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/Annotation/Property/Where.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Annotation/Property/Where.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Annotation\Property; | ||||
| 
 | ||||
| class Where implements \Ulmus\Annotation\Annotation { | ||||
| 
 | ||||
|     public array $comparisons = []; | ||||
| 
 | ||||
|     public function __construct(...$comparisons) | ||||
|     { | ||||
|         if ( $comparisons ) { | ||||
|             $this->comparisons = $comparisons; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										212
									
								
								src/Common/ArrayObjectTrait.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								src/Common/ArrayObjectTrait.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,212 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Common; | ||||
| 
 | ||||
| trait ArrayObjectTrait { | ||||
| 
 | ||||
|     protected $arrayobject_pointer   = null; | ||||
| 
 | ||||
|     protected $arrayobject_container = []; | ||||
| 
 | ||||
|     protected $arrayobject_changed   = []; | ||||
| 
 | ||||
|     protected $arrayobject_selected  = false; | ||||
| 
 | ||||
|     public function count() : int | ||||
|     { | ||||
|         return count( $this->arrayobject_container() ); | ||||
|     } | ||||
| 
 | ||||
|     public function contains($term, $strict = false) : bool | ||||
|     { | ||||
|         return (array_search($term, $this->arrayobject_container(), $strict) !== false) ; | ||||
|     } | ||||
| 
 | ||||
|     public function &arrayobject_current() | ||||
|     { | ||||
|         if ( !is_null($this->arrayobject_pointer) ) { | ||||
|             $var = &$this->arrayobject_container()[$this->arrayobject_pointer] ?: []; | ||||
|             $var || ( $var = [] ); | ||||
|             return $var; | ||||
|         } | ||||
| 
 | ||||
|         if ( $this->arrayobject_selected !== false ){ | ||||
|             $ret = &$this->arrayobject_selected ?: []; | ||||
|             return $ret; | ||||
|         } | ||||
| 
 | ||||
|         # Restoring integrity of container since it could be nullified
 | ||||
|         if ( ! is_array($this->arrayobject_container()) ) { | ||||
|             $this->arrayobject_container([]); | ||||
|         } | ||||
| 
 | ||||
|         return $this->arrayobject_container(); | ||||
|     } | ||||
| 
 | ||||
|     public function offsetSet($offset, $value, $changed = null) | ||||
|     { | ||||
|         if ( $changed && (!isset($this->arrayobject_current()[$offset]) || ($this->arrayobject_current()[$offset] !== $value) ) ) { | ||||
|             $this->arrayobject_changed($offset, true); | ||||
|         } | ||||
| 
 | ||||
|         return is_null($offset) ? $this->arrayobject_current()[] = $value : $this->arrayobject_current()[$offset] = $value; | ||||
|     } | ||||
| 
 | ||||
|     public function arrayobject_set_pointer($pointer) | ||||
|     { | ||||
|         # $pointer could nullify obj pointer
 | ||||
|         if ( $this->arrayobject_pointer = $pointer ) { | ||||
|             # Creating dataset whenever we have a new one
 | ||||
|             if ( ! isset($this->arrayobject_container()[$this->arrayobject_pointer]) ) { | ||||
|                 $this->arrayobject_container()[$this->arrayobject_pointer] = []; | ||||
|                 $this->arrayobject_changed[$this->arrayobject_pointer]   = []; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function arrayobject_select($selection, $purge = true) | ||||
|     { | ||||
|         if ( is_bool($selection) ) { | ||||
|             return $this->arrayobject_selected = $selection; | ||||
|         } | ||||
| 
 | ||||
|         $purge && ( $this->arrayobject_selected = [] ); | ||||
| 
 | ||||
|         foreach($selection as $pointer) { | ||||
|             $this->arrayobject_selected[$pointer] = &$this->arrayobject_container[$pointer]; | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     public function arrayobject_exist($pointer) : bool | ||||
|     { | ||||
|         return isset( $this->arrayobject_container()[$pointer] ); | ||||
|     } | ||||
| 
 | ||||
|     public function arrayobject_flush_changed() { | ||||
|         ! is_null($this->arrayobject_pointer) ? | ||||
|             $this->arrayobject_changed[$this->arrayobject_pointer] = [] | ||||
|         : | ||||
|             $this->arrayobject_changed = [] | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     public function arrayobject_changed($offset = null, $set = null) { | ||||
|         if ($offset) { | ||||
|             if ($set !== null) { | ||||
|                 ! is_null($this->arrayobject_pointer) ? | ||||
|                     ( $this->arrayobject_changed[$this->arrayobject_pointer][$offset] = $set ) | ||||
|                 : | ||||
|                     ( $this->arrayobject_changed[$offset] = $set ); | ||||
|             } | ||||
| 
 | ||||
|             return !is_null($this->arrayobject_pointer) ? $this->arrayobject_changed[$this->arrayobject_pointer][$offset] : $this->arrayobject_changed[$offset]; | ||||
|         } | ||||
| 
 | ||||
|         return array_keys( !is_null($this->arrayobject_pointer) | ||||
|         ?   $this->arrayobject_changed[$this->arrayobject_pointer] ?? [] | ||||
|         :   $this->arrayobject_changed) ?? []; | ||||
|     } | ||||
| 
 | ||||
|     public function arrayobject_remove($pointer) { | ||||
|         if ( isset($this->arrayobject_container()[$pointer]) ) { | ||||
|             unset( $this->arrayobject_container()[$pointer], $this->arrayobject_changed[$pointer]); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function arrayobject_iterate($callback) { | ||||
|         if ( $callback && is_callable($callback) ) { | ||||
|             $pointer = $this->arrayobject_pointer; | ||||
| 
 | ||||
|             foreach($this->arrayobject_container() as $key => $value) { | ||||
|                 $this->arrayobject_set_pointer($key); | ||||
|                 $callback($key, $this); | ||||
|             } | ||||
| 
 | ||||
|             $this->arrayobject_set_pointer($pointer); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function offsetGet($offset) | ||||
|     { | ||||
|         if ( !is_null($this->arrayobject_pointer) ) { | ||||
|             return isset($this->arrayobject_container()[$this->arrayobject_pointer][$offset]) ? $this->arrayobject_container()[$this->arrayobject_pointer][$offset] : null; | ||||
|         } | ||||
|         else { | ||||
|             return isset($this->arrayobject_container()[$offset]) ? $this->arrayobject_container()[$offset] : null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function offsetExists($offset) : bool | ||||
|     { | ||||
|         return array_key_exists($offset, $this->arrayobject_current() ); | ||||
|     } | ||||
| 
 | ||||
|     public function offsetUnset($offset) | ||||
|     { | ||||
|         if ( !is_null($this->arrayobject_pointer)) { | ||||
|             unset($this->arrayobject_container()[$this->arrayobject_pointer][$offset]); | ||||
|         } | ||||
|         else { | ||||
|             unset($this->arrayobject_container()[$offset]) ; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function arrayobject_sort($field, $order = 'ASC') | ||||
|     { | ||||
|         Arrayobj::order_by($this->arrayobject_container(), $field); | ||||
|         $order === 'DESC' && array_reverse($this->arrayobject_current()); | ||||
|     } | ||||
| 
 | ||||
|     public function rewind() | ||||
|     { | ||||
|         reset( $this->arrayobject_container() ); | ||||
| 
 | ||||
|         # Rewinding will also reset the pointer
 | ||||
|         $this->arrayobject_set_pointer(key($this->arrayobject_container())); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function current() | ||||
|     { | ||||
|         return $this->arrayobject_set_pointer( $this->key() ); | ||||
|     } | ||||
| 
 | ||||
|     public function key() | ||||
|     { | ||||
|         $var = key( $this->arrayobject_container() ); | ||||
|         return $var; | ||||
|     } | ||||
| 
 | ||||
|     public function next() | ||||
|     { | ||||
|         $var = next( $this->arrayobject_container() ); | ||||
|         return $var; | ||||
|     } | ||||
| 
 | ||||
|     public function valid() : bool | ||||
|     { | ||||
|         $key = $this->key(); | ||||
|         return ( $key !== NULL ) && ( $key !== FALSE ); | ||||
|     } | ||||
| 
 | ||||
|     protected function &arrayobject_container($set = null) | ||||
|     { | ||||
|         if ( $set !== null ) { | ||||
|             $this->arrayobject_container = $set; | ||||
|         } | ||||
| 
 | ||||
|         if (  $this->arrayobject_selected !== false ) { | ||||
|             return $this->arrayobject_selected; | ||||
|         } | ||||
| 
 | ||||
|         return $this->arrayobject_container; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										56
									
								
								src/Common/EntityField.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/Common/EntityField.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Common; | ||||
| 
 | ||||
| use Ulmus\Ulmus; | ||||
| 
 | ||||
| class EntityField | ||||
| { | ||||
|     public string $name; | ||||
| 
 | ||||
|     public string $entityClass; | ||||
| 
 | ||||
|     public string $alias; | ||||
| 
 | ||||
|     protected EntityResolver $entityResolver; | ||||
| 
 | ||||
|     public function __construct(string $entityClass, string $name, string $alias) | ||||
|     { | ||||
|         $this->entityClass = $entityClass; | ||||
|         $this->name = $name; | ||||
|         $this->alias = $alias; | ||||
|         $this->entityResolver = Ulmus::resolveEntity(static::class); | ||||
|     } | ||||
| 
 | ||||
|     public function name($useAlias = true) : string | ||||
|     { | ||||
|         # Must use REFLECTION before throwing this value.
 | ||||
|         # Should first check if it's a relation field, and if it is,
 | ||||
|         # it's real key must be returned (PK usually)
 | ||||
|         return $useAlias ? "{$this->alias}.`{$this->name}`" : $this->name; | ||||
|     } | ||||
| 
 | ||||
|     public static function isScalarType($type) : bool | ||||
|     { | ||||
|         switch($type) { | ||||
|             case 'int': | ||||
|             case 'bool': | ||||
|             case 'string': | ||||
|             case 'float': | ||||
|             case 'double': | ||||
|                 return true; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     public static function isObjectType($type) : bool | ||||
|     { | ||||
|         return strpos($type, "\\") !== false; | ||||
|     } | ||||
| 
 | ||||
|     public function __toString() : string | ||||
|     { | ||||
|         return $this->name(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										185
									
								
								src/Common/EntityResolver.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								src/Common/EntityResolver.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,185 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Common; | ||||
| 
 | ||||
| use Ulmus\Annotation\Annotation, | ||||
|     Ulmus\Annotation\Classes\Table, | ||||
|     Ulmus\Annotation\Property\Field; | ||||
| 
 | ||||
| class EntityResolver { | ||||
| 
 | ||||
|     const KEY_ENTITY_NAME = 01; | ||||
|     const KEY_COLUMN_NAME = 02; | ||||
| 
 | ||||
|     public string $entityClass; | ||||
| 
 | ||||
|     public array $uses; | ||||
| 
 | ||||
|     public array $class; | ||||
| 
 | ||||
|     public array $properties; | ||||
| 
 | ||||
|     public array $methods; | ||||
| 
 | ||||
|     public function __construct(string $entityClass) | ||||
|     { | ||||
|         $this->entityClass = $entityClass; | ||||
| 
 | ||||
|         list($this->uses, $this->class, $this->methods, $this->properties) = array_values( | ||||
|             ObjectReflection::fromClass($entityClass)->read() | ||||
|         ); | ||||
| 
 | ||||
|         $this->resolveAnnotations(); | ||||
|     } | ||||
| 
 | ||||
|     public function field($name, $fieldKey = self::KEY_ENTITY_NAME) : ? array | ||||
|     { | ||||
|         try{ | ||||
|             return $this->fieldList($fieldKey)[$name]; | ||||
|         } | ||||
|         catch(\Throwable $e) { | ||||
|             throw new \InvalidArgumentException("Can't find entity field's column named `$name` from entity {$this->entityClass}"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function fieldList($fieldKey = self::KEY_ENTITY_NAME) : array | ||||
|     { | ||||
|         $fieldList = []; | ||||
| 
 | ||||
|         foreach($this->properties as $item) { | ||||
|             foreach($item['tags'] ?? [] as $tag) { | ||||
|                 if ( $tag['object'] instanceof Field ) { | ||||
|                     switch($fieldKey) { | ||||
|                         case static::KEY_ENTITY_NAME: | ||||
|                             $key = $item['name']; | ||||
|                         break; | ||||
| 
 | ||||
|                         case static::KEY_COLUMN_NAME: | ||||
|                             $key = $tag['object']->name ?? $item['name']; | ||||
|                         break; | ||||
| 
 | ||||
|                         default: | ||||
|                             throw new \InvalidArgumentException("Given `fieldKey` is unknown to the EntityResolver"); | ||||
|                     } | ||||
| 
 | ||||
|                     $fieldList[$key] = $item; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $fieldList; | ||||
|     } | ||||
| 
 | ||||
|     public function tableName() : string | ||||
|     { | ||||
|         if ( null === $table = $this->getAnnotationFromClassname( Table::class ) ) { | ||||
|             throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation"); | ||||
|         } | ||||
| 
 | ||||
|         if ( $table->name === "" ) { | ||||
|             throw new \ArgumentCountError("Your entity {$this->entityClass} seems to be missing a `name` argument for your @Table() annotation"); | ||||
|         } | ||||
| 
 | ||||
|         return $table->name; | ||||
|     } | ||||
| 
 | ||||
|     public function primaryKeys() : array | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Transform an annotation into it's object's counterpart | ||||
|      */ | ||||
|     public function getAnnotationFromClassname(string $className) : ? object | ||||
|     { | ||||
|         if ( $name = $this->uses[$className] ?? false) { | ||||
| 
 | ||||
|             foreach($this->class['tags'] as $item) { | ||||
|                 if ( $item['tag'] === $name ) { | ||||
|                     return $this->instanciateAnnotationObject($item); | ||||
|                 } | ||||
| 
 | ||||
|                 foreach($this->properties as $item) { | ||||
|                     foreach($item['tags'] as $item) { | ||||
|                         if ( $item['tag'] === $name ) { | ||||
|                             return $this->instanciateAnnotationObject($item); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 foreach($this->methods as $item) { | ||||
|                     foreach($item['tags'] as $item) { | ||||
|                         if ( $item['tag'] === $name ) { | ||||
|                             return $this->instanciateAnnotationObject($item); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             throw new \TypeError("Annotation `$className` could not be found within your object `{$this->entityClass}`"); | ||||
|         } | ||||
|         else { | ||||
|             throw new \InvalidArgumentException("Class `$className` was not found within {$this->entityClass} uses statement (or it's children / traits)"); | ||||
|         } | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public function instanciateAnnotationObject(array $tagDefinition) : Annotation | ||||
|     { | ||||
|         $arguments = $this->extractArguments($tagDefinition['arguments']); | ||||
| 
 | ||||
|         if ( false === $class = array_search($tagDefinition['tag'], $this->uses) ) { | ||||
|             throw new \InvalidArgumentException("Annotation class `{$tagDefinition['tag']}` was not found within {$this->entityClass} uses statement (or it's children / traits)"); | ||||
|         } | ||||
| 
 | ||||
|         $obj = new $class(... $arguments['constructor']); | ||||
| 
 | ||||
|         foreach($arguments['setter'] as $key => $value) { | ||||
|             $obj->$key = $value; | ||||
|         } | ||||
| 
 | ||||
|         return $obj; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Extracts arguments from an Annotation definition, easing object's declaration. | ||||
|      */ | ||||
|     protected function extractArguments(array $arguments) : array | ||||
|     { | ||||
|         $list = [ | ||||
|             'setter' => [], | ||||
|             'constructor' => [], | ||||
|         ]; | ||||
| 
 | ||||
|         ksort($arguments); | ||||
| 
 | ||||
|         foreach($arguments as $key => $value) { | ||||
|             $list[ is_int($key) ? 'constructor' : 'setter' ][$key] = $value; | ||||
|         } | ||||
| 
 | ||||
|         return $list; | ||||
|     } | ||||
| 
 | ||||
|     protected function resolveAnnotations() | ||||
|     { | ||||
|         foreach($this->class['tags'] as &$tag) { | ||||
|             $tag['object'] = $this->instanciateAnnotationObject($tag); | ||||
|         } | ||||
| 
 | ||||
|         foreach($this->properties as &$property) { | ||||
|             foreach($property['tags'] as &$tag){ | ||||
|                 $tag['object'] = $this->instanciateAnnotationObject($tag); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         foreach($this->methods as &$method) { | ||||
|             foreach($method['tags'] as &$tag){ | ||||
|                 $tag['object'] = $this->instanciateAnnotationObject($tag); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										273
									
								
								src/Common/ObjectReflection.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										273
									
								
								src/Common/ObjectReflection.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,273 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Common; | ||||
| 
 | ||||
| use Ulmus\Ulmus; | ||||
| 
 | ||||
| use Ulmus\Annotation\AnnotationReader; | ||||
| 
 | ||||
| use Reflector, ReflectionClass, ReflectionProperty, ReflectionMethod; | ||||
| 
 | ||||
| class ObjectReflection { | ||||
| 
 | ||||
|     public AnnotationReader $annotationReader; | ||||
| 
 | ||||
|     public function __construct($class, AnnotationReader $annotationReader = null) { | ||||
|         $this->classReflection = $class instanceof ReflectionClass ? $class : new ReflectionClass($class); | ||||
|         $this->annotationReader = $annotationReader ?: AnnotationReader::fromClass($class); | ||||
|     } | ||||
| 
 | ||||
|     public static function fromClass($class) : self | ||||
|     { | ||||
|         return new static($class); | ||||
|     } | ||||
| 
 | ||||
|     public function read() : array | ||||
|     { | ||||
|         return [ | ||||
|             'uses'     => $this->gatherUses(true), | ||||
|             'class'    => $this->gatherClass(true), | ||||
|             'method'   => $this->gatherMethods(true), | ||||
|             'property' => $this->gatherProperties(true), | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function gatherUses(bool $full = true) : array | ||||
|     { | ||||
|         $list = []; | ||||
| 
 | ||||
|         if ( $full ) { | ||||
|             if ( $parentClass = $this->classReflection->getParentClass() ) { | ||||
|                 $list = static::fromClass($parentClass)->gatherUses(true); | ||||
|             } | ||||
| 
 | ||||
|             foreach($this->classReflection->getTraits() as $trait) { | ||||
|                 $list = array_merge($list, static::fromClass($trait)->gatherUses(true)); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return array_merge($this->getUsesStatements(), $list); | ||||
|     } | ||||
| 
 | ||||
|     public function gatherClass(bool $full = true) : array | ||||
|     { | ||||
|         $class = []; | ||||
| 
 | ||||
|         if ( $full ) { | ||||
|             if ( $parentClass = $this->classReflection->getParentClass() ) { | ||||
|                 $class = static::fromClass($parentClass)->gatherClass(true); | ||||
|             } | ||||
| 
 | ||||
|             $itemName = function($item) { | ||||
|                 return $item->getName(); | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             'tags' => array_merge($class, $this->annotationReader->getClass($this->classReflection)) | ||||
|         ] + ( ! $full ? [] : [ | ||||
|             'traits' => array_map($itemName, $this->classReflection->getTraits()), | ||||
|             'interfaces' => array_map($itemName, $this->classReflection->getInterfaces()), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public function gatherProperties(bool $full = true, int $filter = | ||||
|         ReflectionProperty::IS_PUBLIC    | | ||||
|         ReflectionProperty::IS_PROTECTED | | ||||
|         ReflectionProperty::IS_PRIVATE | ||||
|     ) : array | ||||
|     { | ||||
|         $properties = []; | ||||
|         $defaultValues = $this->classReflection->getDefaultProperties(); | ||||
| 
 | ||||
|         if ( $full ) { | ||||
|             if ( $parentClass = $this->classReflection->getParentClass() ) { | ||||
|                 $properties = static::fromClass($parentClass)->gatherProperties($full, $filter); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $properties = array_merge($properties, $this->classReflection->getProperties($filter)); | ||||
| 
 | ||||
|         $list = []; | ||||
| 
 | ||||
|         foreach($properties as $property) { | ||||
|             $current = [ | ||||
|                 'name' => $property->getName() | ||||
|             ]; | ||||
| 
 | ||||
|             # Default value can be 'null', so isset() it not suitable here
 | ||||
|             if ( array_key_exists($current['name'], $defaultValues) ) { | ||||
|                 $current['value'] = $defaultValues[ $current['name'] ]; | ||||
|             } | ||||
| 
 | ||||
|             if ( $property->hasType() ) { | ||||
|                 $current['type'] = $property->getType()->getName(); | ||||
|                 $current['nullable'] = $property->getType()->allowsNull(); | ||||
|             } | ||||
| 
 | ||||
|             $current['tags'] = $this->annotationReader->getProperty($property); | ||||
| 
 | ||||
|             if ( $this->ignoreElementAnnotation($current['tags']) ) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             $list[ $current['name'] ] = $current; | ||||
|         } | ||||
| 
 | ||||
|         return $list; | ||||
|     } | ||||
| 
 | ||||
|     public function gatherMethods(bool $full = true, int $filter = | ||||
|         ReflectionMethod::IS_PUBLIC    | | ||||
|         ReflectionMethod::IS_PROTECTED | | ||||
|         ReflectionMethod::IS_PRIVATE   | | ||||
|         ReflectionMethod::IS_STATIC | ||||
|     ) : array | ||||
|     { | ||||
|         $methods = []; | ||||
| 
 | ||||
|         if ( $full ) { | ||||
|             if ( $parentClass = $this->classReflection->getParentClass() ) { | ||||
|                 $methods = static::fromClass($parentClass)->gatherMethods($full, $filter); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $methods = array_merge($methods, $this->classReflection->getMethods($filter)); | ||||
| 
 | ||||
|         $list = []; | ||||
| 
 | ||||
|         foreach($methods as $method) { | ||||
|             $parameters = []; | ||||
| 
 | ||||
|             foreach($method->getParameters() as $parameter) { | ||||
|                 $parameters[$parameter->getName()] = [ | ||||
|                     'null' => $parameter->allowsNull(), | ||||
|                     'position' => $parameter->getPosition(), | ||||
|                     'type' => $parameter->hasType() ? $parameter->getType()->getName() : false, | ||||
|                     'array' => $parameter->isArray(), | ||||
|                     'callable' => $parameter->isCallable(), | ||||
|                     'optional' => $parameter->isOptional(), | ||||
|                     'byReference' => $parameter->isPassedByReference(), | ||||
|                 ]; | ||||
|             } | ||||
| 
 | ||||
|             $current = [ | ||||
|                 'name' => $method->getName(), | ||||
|                 'type' => $method->hasReturnType() ? $method->getReturnType()->getName() : false, | ||||
|                 'constructor' => $method->isConstructor(), | ||||
|                 'destructor' => $method->isDestructor(), | ||||
|                 'parameters' => $parameters, | ||||
|             ]; | ||||
| 
 | ||||
|             $current['tags'] = $this->annotationReader->getMethod($method); | ||||
| 
 | ||||
|             if ( $this->ignoreElementAnnotation($current['tags']) ) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             $list[ $current['name'] ] = $current; | ||||
|         } | ||||
| 
 | ||||
|         return $list; | ||||
|     } | ||||
| 
 | ||||
|     protected function ignoreElementAnnotation($tags) : bool | ||||
|     { | ||||
|         return in_array('IGNORE', array_map('strtoupper', array_column($tags, 'tag') )); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     protected function readCode() : string | ||||
|     { | ||||
|         static $code = []; | ||||
|         $fileName = $this->classReflection->getFilename(); | ||||
|         return $code[$fileName] ?? $code[$fileName] = file_get_contents($fileName); | ||||
|     } | ||||
| 
 | ||||
|     protected function getUsesStatements() : array | ||||
|     { | ||||
|         $uses = []; | ||||
|         $tokens = token_get_all( $c = $this->readCode() ); | ||||
| 
 | ||||
|         while ( $token = array_shift($tokens) ) { | ||||
| 
 | ||||
|             if ( is_array($token) ) { | ||||
|                 list($token, $value) = $token; | ||||
|             } | ||||
| 
 | ||||
|             switch ($token) { | ||||
|                 case T_CLASS: | ||||
|                 case T_TRAIT: | ||||
|                 case T_INTERFACE: | ||||
|                     break 2; | ||||
| 
 | ||||
|     			case T_USE: | ||||
|                     $isUse = true; | ||||
| 				break; | ||||
| 
 | ||||
|                 case T_NS_SEPARATOR: | ||||
|                     $isNamespace = $isUse; | ||||
|                 break; | ||||
| 
 | ||||
|                 case T_STRING: | ||||
|                     if ( $isNamespace && $latestString ) { | ||||
|                         $statement[] = $latestString; | ||||
|                     } | ||||
| 
 | ||||
|                     $latestString = $value; | ||||
|                 break; | ||||
| 
 | ||||
|                 case T_AS: | ||||
|                     # My\Name\Space\aClassHere `as` ClassAlias;
 | ||||
|                     $replacedClass = implode("\\", array_merge($statement, [ $latestString ])); | ||||
|                     $latestString = null; | ||||
|                 break; | ||||
| 
 | ||||
|                 case T_WHITESPACE: | ||||
|                 case T_COMMENT: | ||||
|                 case T_DOC_COMMENT: | ||||
|                 break; | ||||
| 
 | ||||
|                 case '{': | ||||
|                     # opening a sub-namespace -> \My\Name\Space\`{`OneItem, AnotherItem}
 | ||||
|                     if ( $isNamespace ) { | ||||
|                         $inNamespace = true; | ||||
|                     } | ||||
|                 break; | ||||
| 
 | ||||
|                 case ';'; | ||||
|                 case ',': | ||||
|                 case '}': | ||||
|                     if ( $isUse ) { | ||||
|                         if ( $replacedClass ) { | ||||
|                             $uses[$replacedClass] = $latestString; | ||||
|                             $replacedClass = ""; | ||||
|                         } | ||||
|                         elseif ( $latestString ) { | ||||
|                             $uses[implode("\\", array_merge($statement, [ $latestString ]))] = $latestString; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     if ( $inNamespace ) { | ||||
|                         $latestString = ""; | ||||
| 
 | ||||
|                         # \My\Name\Space\{OneItem, AnotherItem`}` <- closing a sub-namespace
 | ||||
|                         if ( $token !== "}" ) { | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                 case T_OPEN_TAG: | ||||
|                 default: | ||||
|                     $statement = []; | ||||
|                     $latestString = ""; | ||||
|                     $replacedClass = null; | ||||
|                     $isNamespace = $inNamespace = false; | ||||
|                     $isUse = ( $isUse ?? false ) && ( $token === ',' ); | ||||
|                 break; | ||||
|             } | ||||
| 		} | ||||
| 
 | ||||
|         return $uses; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										45
									
								
								src/Common/PdoObject.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/Common/PdoObject.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Common; | ||||
| 
 | ||||
| use PDO, PDOStatement; | ||||
| 
 | ||||
| class PdoObject extends PDO { | ||||
| 
 | ||||
|     public function select(string $sql, array $parameters = []) : PDOStatement | ||||
|     { | ||||
|         try { | ||||
|             if ( $statement = $this->prepare($sql) ) { | ||||
|                 $statement = $this->execute($statement, $parameters, true); | ||||
|                 $statement->setFetchMode(\PDO::FETCH_ASSOC); | ||||
|                 return $statement; | ||||
|             } | ||||
|         } catch (\PDOException $e) { throw $e; } | ||||
|     } | ||||
| 
 | ||||
|     public function execute(PDOStatement $statement, array $parameters = [], bool $commit = true) : ? PDOStatement | ||||
|     { | ||||
|        try { | ||||
|            if (! $this->inTransaction() ) { | ||||
|                $this->beginTransaction(); | ||||
|            } | ||||
|             | ||||
|            if ( empty($parameters) ? $statement->execute() : $statement->execute($parameters) ) { | ||||
|               # if ( $commit ) {
 | ||||
|                    $this->commit(); | ||||
|               # }
 | ||||
| 
 | ||||
|                return $statement; | ||||
|            } | ||||
|            else { | ||||
|                throw new PDOException('Could not begin transaction or given statement is invalid.'); | ||||
|            } | ||||
|        } catch (\PDOException $e) { | ||||
|            $this->rollback(); | ||||
|            throw $e; | ||||
|        } | ||||
| 
 | ||||
|        return null; | ||||
|    } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										67
									
								
								src/Common/Sql.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/Common/Sql.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Common; | ||||
| 
 | ||||
| abstract class Sql { | ||||
| 
 | ||||
|     public static function function($name, ...$arguments) | ||||
|     { | ||||
|         return new class($name, ...$arguments) { | ||||
| 
 | ||||
|             protected string $as = ""; | ||||
| 
 | ||||
|             protected string $name; | ||||
| 
 | ||||
|             protected array $arguments; | ||||
| 
 | ||||
|             public function __construct(string $name, ...$arguments) { | ||||
|                 $this->name = $name; | ||||
|                 $this->arguments = $arguments; | ||||
|                 $this->parseArguments(); | ||||
|             } | ||||
| 
 | ||||
|             public function __toString() { | ||||
|                 return implode(' ', array_filter([ | ||||
|                     "{$this->name}(" . implode(", ", $this->arguments) . ")", | ||||
|                     $this->as ? "AS {$this->as}" : false, | ||||
|                 ])); | ||||
|             } | ||||
| 
 | ||||
|             public function as($fieldName) { | ||||
|                 $this->as = $fieldName; | ||||
|                 return $this; | ||||
|             } | ||||
| 
 | ||||
|             protected function parseArguments() { | ||||
|                 foreach($this->arguments as &$item) { | ||||
|                     $item = Sql::escape($item); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     public static function escape($value) | ||||
|     { | ||||
|         switch(true) { | ||||
|             case is_object($value): | ||||
|                 # @TODO Make sure the object is a Field
 | ||||
|                 return (string) $value; | ||||
|             break; | ||||
| 
 | ||||
|             case is_string($value): | ||||
|                 $value = "\"$value\"";
 | ||||
|             break; | ||||
| 
 | ||||
|             case is_null($value): | ||||
|                 $value = "NULL"; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         return $value; | ||||
|     } | ||||
| 
 | ||||
|     public static function parameter($value) : string | ||||
|     { | ||||
|          | ||||
|     } | ||||
| } | ||||
							
								
								
									
										87
									
								
								src/ConnectionAdapter.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/ConnectionAdapter.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,87 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus; | ||||
| 
 | ||||
| use Ulmus\Adapter\AdapterInterface; | ||||
| 
 | ||||
| use Ulmus\Common\PdoObject; | ||||
| 
 | ||||
| class ConnectionAdapter | ||||
| { | ||||
|     public string $name; | ||||
| 
 | ||||
|     public array $configuration; | ||||
| 
 | ||||
|     protected AdapterInterface $adapter; | ||||
| 
 | ||||
|     public PdoObject $pdo; | ||||
| 
 | ||||
|     public function __construct(string $name = "default", array $configuration = []) | ||||
|     { | ||||
|         $this->name = $name; | ||||
|         $this->configuration = $configuration; | ||||
| 
 | ||||
|         if ( $name === "default" ) { | ||||
|             Ulmus::$defaultAdapter = $this; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function resolveConfiguration() | ||||
|     { | ||||
|         $connection = $this->configuration['connections'][$this->name] ?? []; | ||||
| 
 | ||||
|         if ( $adapterName = $connection['adapter'] ?? false ) { | ||||
|             $this->adapter = $this->instanciateAdapter($adapterName); | ||||
|         } | ||||
|         else { | ||||
|             throw new \InvalidArgumentException("Adapter not found within your configuration array."); | ||||
|         } | ||||
| 
 | ||||
|         if ( false === $this->adapter->hostname = $connection['host'] ?? false ) { | ||||
|             throw new \InvalidArgumentException("Your `host` name is missing from your configuration array"); | ||||
|         } | ||||
| 
 | ||||
|         if ( false === $this->adapter->port = $connection['port'] ?? false ) { | ||||
|             throw new \InvalidArgumentException("Your `port` number is missing from your configuration array"); | ||||
|         } | ||||
| 
 | ||||
|         if ( false === $this->adapter->database = $connection['database'] ?? false ) { | ||||
|             throw new \InvalidArgumentException("Your `database` name is missing from your configuration array"); | ||||
|         } | ||||
| 
 | ||||
|         if ( false === $this->adapter->username = $connection['username'] ?? false ) { | ||||
|             throw new \InvalidArgumentException("Your `username` is missing from your configuration array"); | ||||
|         } | ||||
| 
 | ||||
|         if ( false === $this->adapter->password = $connection['password'] ?? false ) { | ||||
|             throw new \InvalidArgumentException("Your `password` is missing from your configuration array"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Connect the adapter | ||||
|      * @return self | ||||
|      */ | ||||
|     public function connect() | ||||
|     { | ||||
|         $this->pdo = $this->adapter->connect(); | ||||
|         $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); | ||||
|         $this->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); | ||||
|         $this->pdo->setAttribute(\PDO::ATTR_AUTOCOMMIT, false); | ||||
|         $this->pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC); | ||||
|         $this->pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Instanciate an adapter which interact with the data source | ||||
|      * @param  string $name An Ulmus adapter or full class name implementing AdapterInterface | ||||
|      * @return AdapterInterface | ||||
|      */ | ||||
|     protected function instanciateAdapter($name) | ||||
|     { | ||||
|         $class = substr($name, 0, 2) === "\\" ? $name : "\\Ulmus\\Adapter\\$name"; | ||||
|         return new $class(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										8
									
								
								src/EntityCollection.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/EntityCollection.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus; | ||||
| 
 | ||||
| class EntityCollection extends \ArrayObject | ||||
| { | ||||
|     use Common\ArrayObjectTrait; | ||||
| } | ||||
							
								
								
									
										82
									
								
								src/EntityTrait.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/EntityTrait.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus; | ||||
| 
 | ||||
| use Ulmus\Repository, | ||||
|     Ulmus\Common\EntityResolver, | ||||
|     Ulmus\Common\EntityField; | ||||
| 
 | ||||
| use Ulmus\Annotation\Classes\{ Method, Table, Collation as Test, }; | ||||
| use Ulmus\Annotation\Property\{ Field, Relation, OrderBy, Where, }; | ||||
| use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, }; | ||||
| 
 | ||||
| trait EntityTrait { | ||||
| 
 | ||||
|     public function entityFillFromDataset($dataset) : self | ||||
|     { | ||||
|         $fields = Ulmus::resolveEntity(static::class); | ||||
| 
 | ||||
|         foreach($dataset as $key => $value) { | ||||
|             if ( null === $field = $fields->field($key, EntityResolver::KEY_COLUMN_NAME) ?? null ) { | ||||
|                 throw new \Exception("Field `$key` can not be found within your entity ".static::class); | ||||
|             } | ||||
| 
 | ||||
|             if ( is_null($value) ) { | ||||
|                 $this->{$field['name']} = null; | ||||
|             } | ||||
|             elseif ( $field['type'] === 'array' ) { | ||||
|                 $this->{$field['name']} = substr($value, 0, 1) === "a" ? unserialize($value) : json_decode($value, true); | ||||
|             } | ||||
|             elseif ( EntityField::isScalarType($field['type']) ) { | ||||
|                 $this->{$field['name']} = $value; | ||||
|             } | ||||
|             elseif ( EntityField::isObjectType($field['type']) ) { | ||||
|                 $this->{$field['name']} = new $field['type'](); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @Ignore | ||||
|      */ | ||||
|     public static function repository() : Repository | ||||
|     { | ||||
|         return Ulmus::repository(static::class); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @Ignore | ||||
|      */ | ||||
|     public static function queryBuilder() : QueryBuilder | ||||
|     { | ||||
|         return Ulmus::queryBuilder(static::class); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @Ignore | ||||
|      */ | ||||
|     public static function field($name, ? string $alias = null) | ||||
|     { | ||||
|         return new EntityField(static::class, $name, $alias ?: Repository::DEFAULT_ALIAS); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @Ignore | ||||
|      */ | ||||
|     public static function fields(...$fields) | ||||
|     { | ||||
|         return implode(', ', array_map(function($name) { | ||||
|             return static::field($name); | ||||
|         }, $fields)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @Ignore | ||||
|      */ | ||||
|     public static function table() | ||||
|     { | ||||
|         return "REFLECT TABLE"; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										0
									
								
								src/Modeler/Field.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/Modeler/Field.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										404
									
								
								src/Modeler/Query.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								src/Modeler/Query.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,404 @@ | ||||
| <?php namespace Alive\Storage\Sql; | ||||
| 
 | ||||
| use Alive\{ | ||||
|     constructor, | ||||
|     Arrayobj | ||||
| }; | ||||
| 
 | ||||
| class QueryBuilder { | ||||
|     protected $fields; | ||||
|     protected $where; | ||||
|     protected $order_by; | ||||
|     protected $group_by; | ||||
|     protected $limit; | ||||
| 
 | ||||
|     public static $syntax = [ | ||||
|         'against'  => 'AGAINST', | ||||
|         'and'      => 'AND', | ||||
|         'as'       => 'AS', | ||||
|         'charset'  => 'CHARACTER SET', | ||||
|         'collate'  => 'COLLATE', | ||||
|         'create'   => 'CREATE', | ||||
|         'database' => 'DATABASE', | ||||
|         'delete'   => 'DELETE FROM', | ||||
|         'distinct' => 'DISTINCT', | ||||
|         'drop'     => 'DROP', | ||||
|         'engine'   => 'ENGINE', | ||||
|         '!exist'   => 'IF NOT EXISTS', | ||||
|         'exist'    => 'IF EXISTS', | ||||
|         'explain'  => 'EXPLAIN', | ||||
|         'from'     => 'FROM', | ||||
|         'grant'    => 'GRANT', | ||||
|         'grant_option' => 'GRANT OPTION', | ||||
|         'group_by' => 'GROUP BY', | ||||
|         'having'   => 'HAVING', | ||||
|         'in'       => 'IN', | ||||
|         'insert'   => 'INSERT INTO', | ||||
|         'join'     => 'JOIN', | ||||
|         'join-left' => 'LEFT', | ||||
|         'join-right' => 'RIGHT', | ||||
|         'join-inner' => 'INNER', | ||||
|         'join-full'  => 'FULL', | ||||
|         'join-self'  => 'SELF', | ||||
|         #'join-outer' => 'OUTER',
 | ||||
|         'join-cross' => 'CROSS', | ||||
|         'like'     => 'LIKE', | ||||
|         'limit'    => 'LIMIT', | ||||
|         'match'    => 'MATCH', | ||||
|         'not_in'   => 'NOT IN', | ||||
|         'on'       => 'ON', | ||||
|         'on_table' => 'ON TABLE', | ||||
|         'or'       => 'OR', | ||||
|         'order_by' => 'ORDER BY', | ||||
|         'offset'   => 'OFFSET', | ||||
|         'revoke'   => 'REVOKE', | ||||
|         'select'   => 'SELECT', | ||||
|         'set'      => 'SET', | ||||
|         'table'    => 'TABLE', | ||||
|         'table_charset' => 'DEFAULT CHARSET', | ||||
|         'to'       => 'TO', | ||||
|         'update'   => 'UPDATE', | ||||
|         'values'   => 'VALUES', | ||||
|         'where'    => 'WHERE' | ||||
|     ]; | ||||
| 
 | ||||
|     static $escape_char = '`'; | ||||
| 
 | ||||
|     protected $compiled = []; | ||||
| 
 | ||||
|     public static function select($param) { | ||||
|         $param = Arrayobj::make($param); | ||||
| 
 | ||||
|         return static::prepare_array([ | ||||
|             $param->if_has('explain'  , static::$syntax['explain']), | ||||
|             static::$syntax['select'], | ||||
|             $param->if_has('distinct' , static::$syntax['distinct']), | ||||
|             static::group_fields($param['fields'] ?: '*'), | ||||
|             static::$syntax['from'], | ||||
|             static::full_tablename($param), | ||||
|             static::prepare_join($param['join']), | ||||
|             static::prepare_where($param['where'], false, $param->ternary('escaped', true)), | ||||
|             $param->if_has('group_by' , static::prepare_group($param['group_by'], $param['alias'] ?? null)), | ||||
|             $param->if_has('having'   , static::$syntax['having']." {$param['having']}"), | ||||
|             /*  @todo  UNION | INTERSECT | EXCEPT  GOES HERE !*/ | ||||
|             $param->if_has('order_by' , static::prepare_order($param['order_by'], $param['alias'] ?? null)), | ||||
|             static::prepare_limit($param) | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This function will translate parameters into a "create database" or "create table", depending | ||||
|      * on given param. | ||||
|      * | ||||
|      * @param array $param 'subject': table or database | ||||
|      * | ||||
|      * @return Type    Description | ||||
|      */ | ||||
|     public static function create($param) { | ||||
|         $param = Arrayobj::make($param); | ||||
|         return strtolower( $param['subject'] ) === 'table' ? static::create_table($param) : static::create_database($param); | ||||
|     } | ||||
| 
 | ||||
|     public static function create_table($param) { | ||||
|         $param = is_array($param) ? Arrayobj::make($param) : $param; | ||||
| 
 | ||||
|         return static::prepare_array([ | ||||
|             static::$syntax['create'], | ||||
|             static::$syntax['table'], | ||||
|             $param->if_has('!exist', static::$syntax['!exist']), | ||||
|             static::full_tablename($param), | ||||
|             static::group_create_fields($param->mandatory('fields'), true), | ||||
|             $param->if_has('collation' , static::$syntax['collate']." {$param['collation']}" ) | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public static function create_database($param) { | ||||
|         $param = is_array($param) ? Arrayobj::make($param) : $param; | ||||
| 
 | ||||
|         return static::prepare_array([ | ||||
|             static::$syntax['create'], | ||||
|             static::$syntax['database'], | ||||
|             $param->if_has('!exist', static::$syntax['!exist']), | ||||
|             static::escape( $param->mandatory('database') ) | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public static function insert($param) { | ||||
|         $param = Arrayobj::make($param); | ||||
| 
 | ||||
|         $field_label  = static::group_fields( $param->mandatory('fields'), true, true ); | ||||
|         $field_values = static::group_values( $param->mandatory('values'), $param['escaped'] ?: false ); | ||||
| 
 | ||||
|         return static::prepare_array([ | ||||
|             static::$syntax['insert'], | ||||
|             static::full_tablename($param), | ||||
|             $field_label, | ||||
|             static::$syntax['values'], | ||||
|             $field_values | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public static function grant($param) { | ||||
|         $param = Arrayobj::make($param); | ||||
| 
 | ||||
|         $field_label = static::group_fields( $param->mandatory('privileges') ); | ||||
|         $users = static::group_fields( $param->mandatory('users') ); | ||||
| 
 | ||||
|         return static::prepare_array([ | ||||
|             static::$syntax['grant'], | ||||
|             $field_label, | ||||
|             static::$syntax['on_table'], | ||||
|             static::full_tablename($param), | ||||
|             static::$syntax['to'], | ||||
|             $users, | ||||
|             $param->if_has('grant_option', static::$syntax['grant_option']) | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public static function delete($param) { | ||||
|         $param = Arrayobj::make($param); | ||||
| 
 | ||||
|         return static::prepare_array([ | ||||
|             static::$syntax['delete'], | ||||
|             static::full_tablename($param), | ||||
|             static::prepare_where($param['where'], false, $param->ternary('escaped', true)), | ||||
|             static::prepare_order($param), | ||||
|             static::prepare_limit($param) | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public static function update($param) { | ||||
|         $param = Arrayobj::make($param); | ||||
| 
 | ||||
|         $fields = static::group_values_and_fields($param->mandatory('fields'), $param->mandatory('values')); | ||||
| 
 | ||||
|         return static::prepare_array([ | ||||
|             static::$syntax['update'], | ||||
|             static::full_tablename($param), | ||||
|             static::$syntax['set'], | ||||
|             $fields, | ||||
|             static::prepare_where($param['where']) | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public static function drop($param) { | ||||
|         $param = Arrayobj::make($param); | ||||
| 
 | ||||
|         return static::prepare_array([ | ||||
|             static::$syntax['drop'], | ||||
|             $param->exist('table_name') ? static::$syntax['table']." ".static::full_tablename($param) : static::$syntax['database']." ".static::escape($param->mandatory('database')) | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public static function full_tablename($param) { | ||||
|         is_array($param) && ($param = Arrayobj::make($param)); | ||||
|         return $param->if_has('database', static::escape($param['database']).".") . static::escape($param->mandatory('table_name')) . $param->if_has('alias', " ".static::$syntax['as']." " . $param['alias']); | ||||
|     } | ||||
| 
 | ||||
|     public static function group_fields($fields, $enclose = false, $escape = false) { | ||||
|         if (is_array($fields)) { | ||||
|             return ($enclose ? "(" : "") .implode(', ', $escape ? array_map(function($item){ return static::escape($item); }, $fields) : $fields).($enclose ? ")" : ""); | ||||
|         } | ||||
|         else { | ||||
|             return $escape ? static::escape($fields) : $fields; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static function group_create_fields($fields, $enclose = false) { | ||||
|         if (is_array($fields)) { | ||||
|             $retval = []; | ||||
| 
 | ||||
|             foreach($fields as $key => $value) { | ||||
|                 $retval[] = static::escape($key)." ".$value; | ||||
|             } | ||||
| 
 | ||||
|             return ($enclose ? "(" : "") .implode(', ', $retval).($enclose ? ")" : ""); | ||||
|         } | ||||
|         else { | ||||
|             return $fields; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static function group_values($values, $escaped = false) { | ||||
|         $tmp = array_pop($values); | ||||
|         array_push($values, $tmp); | ||||
| 
 | ||||
|         # Are we dealing with an array of values ?
 | ||||
|         if ( is_array($tmp) ) { | ||||
|             $retval = []; | ||||
| 
 | ||||
|             foreach($values as $item) { | ||||
|                 $retval[] = implode(', ', $escaped ? $item : static::escape_values($item) ); | ||||
|             } | ||||
| 
 | ||||
|             return "(".implode('), (', $retval).")"; | ||||
|         } | ||||
|         else { | ||||
|             return "(".implode(', ', $escaped ? $values : static::escape_values($values)).")"; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static function escape_values($values) { | ||||
|         $type_function = function(& $item) { | ||||
| 
 | ||||
|             switch( $t = gettype($item) ) { | ||||
|                 case "boolean": | ||||
|                     $item = $item ? 1 : 0; | ||||
|                     break; | ||||
| 
 | ||||
|                 case "double": | ||||
|                 case "integer": | ||||
|                     break; | ||||
| 
 | ||||
|                 case "NULL": | ||||
|                     $item = "NULL"; | ||||
|                     break; | ||||
| 
 | ||||
|                 case "string": | ||||
|                     $item = "\"$item\"";
 | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             return $item; | ||||
|         }; | ||||
| 
 | ||||
|         return is_array($values) ? array_map($type_function, $values) : $type_function($values); | ||||
|     } | ||||
| 
 | ||||
|     public static function group_values_and_fields($fields, $values) { | ||||
|         $retval = []; | ||||
| 
 | ||||
|         foreach($fields as $key => $item) { | ||||
|             $retval[] = "{$item} = {$values[$key]}"; | ||||
|         } | ||||
| 
 | ||||
|         return implode(', ', $retval); | ||||
|     } | ||||
| 
 | ||||
|     public static function prepare_array($sql) { | ||||
|         return implode(" ", array_filter($sql)).";"; | ||||
|     } | ||||
| 
 | ||||
|     public static function prepare_where($where, $recursion = false, $escaped = false) { | ||||
|         $retval = []; | ||||
| 
 | ||||
|         if (is_array($where)) { | ||||
|             $count = count($where); | ||||
|             for($i = 0; $i < $count; $i++) { | ||||
|                 $item = $where[$i]; | ||||
| 
 | ||||
|                 if ( ! Arrayobj::array_is_associative($item) ) { | ||||
|                     $retval[] = "(".static::prepare_where($item, true, $escaped).")"; | ||||
|                 } | ||||
|                 else { | ||||
|                     $comparison = (isset($item['comparison']) ? $item['comparison'] : "="); | ||||
| 
 | ||||
|                     # are we having an IN comparison here ...
 | ||||
|                     if ( $is_array = (is_array($item['value']) && count($item['value']) > 1) ) { | ||||
|                         switch ($item['comparison']) { | ||||
|                             case '=': | ||||
|                                 $comparison = '='; | ||||
|                                 break; | ||||
| 
 | ||||
|                             case '!=': | ||||
|                                 $comparison = 'not_in'; | ||||
|                                 break; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     $value = static::group_fields($item['value'], true); | ||||
| 
 | ||||
| 
 | ||||
|                     switch($comparison) { | ||||
|                         case 'match': | ||||
|                             $retval[] = static::$syntax[$comparison].' ('.static::fieldname($item['field'], $item['alias'] ?? null).") ".static::$syntax['against']. | ||||
|                                 " (".(!$escaped || $is_array ? $value : static::escape_values($value))." IN BOOLEAN MODE)". | ||||
|                                 ($i + 1 < $count ? " ".static::$syntax[ isset($item['operator']) ? $item['operator'] : "and" ] : ""); | ||||
| 
 | ||||
|                             break; | ||||
| 
 | ||||
|                         default: | ||||
|                             $retval[] = static::fieldname($item['field'], $item['alias'] ?? null)." " . ( isset(static::$syntax[$comparison]) ? static::$syntax[$comparison] : $comparison) . | ||||
|                                 " ".(!$escaped || $is_array ? $value : static::escape_values($value)). | ||||
|                                 ($i + 1 < $count ? " ".static::$syntax[ isset($item['operator']) ? $item['operator'] : "and" ] : ""); | ||||
|                             break; | ||||
| 
 | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $retval ? ($recursion ? "" : static::$syntax['where'] . " ") . implode(" ", $retval ) : ""; | ||||
|     } | ||||
| 
 | ||||
|     public static function prepare_join($joins) { | ||||
|         $retval = []; | ||||
| 
 | ||||
|         if ( is_array($joins) ) { | ||||
|             $count = count($joins); | ||||
| 
 | ||||
|             for($i = 0; $i < $count; $i++) { | ||||
|                 $join = []; | ||||
| 
 | ||||
|                 $table = Arrayobj::make([ | ||||
|                     'table_name' => $joins[$i]['table'], | ||||
|                     'alias'      => $joins[$i]['alias_right'] | ||||
|                 ]); | ||||
| 
 | ||||
|                 $join[] = static::$syntax[ "join-".$joins[$i]['type'] ] ?? $joins[$i]['type']; | ||||
|                 $join[] = static::$syntax[ 'join' ]; | ||||
|                 $join[] = static::full_tablename($table); | ||||
|                 $join[] = static::$syntax[ 'on' ]; | ||||
| 
 | ||||
|                 foreach($joins[$i]['fields'] as $left_field => $right_field) { | ||||
|                     #$join[] = $joins[$i]['alias_left'].".".static::escape($left_field);
 | ||||
|                     $join[] = static::fieldname($left_field, $joins[$i]['alias_left']); | ||||
|                     $join[] = $joins[$i]['comparison']; | ||||
|                     $join[] = static::fieldname($right_field, $joins[$i]['alias_right']); | ||||
|                 } | ||||
| 
 | ||||
|                 $retval[] = implode(' ', $join); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         return implode(' ', $retval); | ||||
|     } | ||||
| 
 | ||||
|     public static function prepare_order($order, $alias = null) | ||||
|     { | ||||
|         $retval = []; | ||||
| 
 | ||||
|         if (is_array($order)) { | ||||
|             foreach($order as $item) { | ||||
|                 $retval[] = static::fieldname($item['field'], $alias).( !empty($item['order']) ? " ".$item['order'] : "" ); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $retval ?  static::$syntax['order_by']." ".implode(', ', $retval) : ""; | ||||
|     } | ||||
| 
 | ||||
|     public static function prepare_group($group) | ||||
|     { | ||||
|         return $group ? static::$syntax['group_by']." ".( is_array($group) ? implode(', ', $group) : $group ) : ""; | ||||
|     } | ||||
| 
 | ||||
|     public static function prepare_limit($param) | ||||
|     { | ||||
|         return implode(' ', array_filter([ | ||||
|             $param->if_has('limit' , static::$syntax['limit'] ." {$param['limit']}"), | ||||
|             $param->if_has('offset', static::$syntax['offset']." {$param['offset']}") | ||||
|         ])); | ||||
|     } | ||||
| 
 | ||||
|     public static function fieldname($field, $alias = null) | ||||
|     { | ||||
|         return strpos($field, '.') ? $field : (!empty($alias) ? $alias."." : "").static::escape($field); | ||||
|     } | ||||
| 
 | ||||
|     public static function escape($field) | ||||
|     { | ||||
|         return static::$escape_char . str_replace(static::$escape_char, '', $field) . static::$escape_char; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										21
									
								
								src/Modeler/Schema.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/Modeler/Schema.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Modeler; | ||||
| 
 | ||||
| class Schema { | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
|          | ||||
|     } | ||||
| 
 | ||||
|     public function compare() | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function migrate() | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								src/Query/Explain.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/Query/Explain.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query; | ||||
| 
 | ||||
| class Explain extends Fragment { | ||||
| 
 | ||||
|     public int $order = -1000; | ||||
| 
 | ||||
|     public bool $extended = false; | ||||
| 
 | ||||
|     public function render() : string | ||||
|     { | ||||
|         return $this->renderSegments([ | ||||
|             "EXPLAIN", $this->extended ? "EXTENDED" : "" | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/Query/Fragment.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Query/Fragment.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query; | ||||
| 
 | ||||
| abstract class Fragment { | ||||
| 
 | ||||
|     public int $order = 0; | ||||
| 
 | ||||
|     public abstract function render() : string; | ||||
| 
 | ||||
|     protected function renderSegments(array $segments, string $glue = " ") : string | ||||
|     { | ||||
|         return implode($glue, array_filter($segments)); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										43
									
								
								src/Query/From.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/Query/From.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query; | ||||
| 
 | ||||
| class From extends Fragment { | ||||
| 
 | ||||
|     public int $order = -80; | ||||
| 
 | ||||
|     protected $tables = []; | ||||
| 
 | ||||
|     public function set(array $tables) : self | ||||
|     { | ||||
|         $this->tables = $tables; | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function add($table) : self | ||||
|     { | ||||
|         foreach((array) $table as $alias => $name) { | ||||
|             $this->tables[$alias] = $name; | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function render() : string | ||||
|     { | ||||
|         return $this->renderSegments([ | ||||
|             'FROM', $this->renderTables(), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     protected function renderTables() : string | ||||
|     { | ||||
|         $list = []; | ||||
| 
 | ||||
|         foreach((array) $this->tables as $alias => $table) { | ||||
|             $list[] = ! is_numeric($alias) ? "`$table` $alias" : "`$table`"; | ||||
|         } | ||||
| 
 | ||||
|         return implode(", ", $list); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/Query/GroupBy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/Query/GroupBy.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query; | ||||
| 
 | ||||
| class GroupBy extends Fragment { | ||||
|     public int $order = 70; | ||||
| 
 | ||||
|     public array $groupBy = []; | ||||
| 
 | ||||
|     public function set(array $order) : self | ||||
|     { | ||||
|         $this->groupBy = $order; | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function add(string $field, ? string $direction = null) : self | ||||
|     { | ||||
|         $this->groupBy[] = $field; | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function render() : string | ||||
|     { | ||||
|         return $this->renderSegments([ | ||||
|             'GROUP BY', implode(", ", $this->groupBy) | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/Query/Having.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/Query/Having.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query; | ||||
| 
 | ||||
| class Having extends Where { | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										9
									
								
								src/Query/Insert.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/Query/Insert.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query; | ||||
| 
 | ||||
| class Insert extends Fragment { | ||||
| 
 | ||||
|     public bool $ignore = false; | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										28
									
								
								src/Query/Join.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/Query/Join.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query; | ||||
| 
 | ||||
| class Join extends Fragment { | ||||
| 
 | ||||
|     const TYPE_LEFT = "LEFT"; | ||||
|     const TYPE_RIGHT = "RIGHT"; | ||||
|     const TYPE_INNER = "INNER"; | ||||
|     const TYPE_FULL = "FULL"; | ||||
|     const TYPE_CROSS = "CROSS"; | ||||
|     const TYPE_NATURAL = "NATURAL"; | ||||
| 
 | ||||
|     public string $type = self::TYPE_INNER; | ||||
| 
 | ||||
|     public bool $outer = false; | ||||
| 
 | ||||
|     public function render() : string | ||||
|     { | ||||
|         return $this->renderSegments([ | ||||
|             $this->side, | ||||
|             'JOIN', | ||||
|             /* table here! */, | ||||
|             'ON', | ||||
|             /* WHERE ! */ | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/Query/Like.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/Query/Like.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query; | ||||
| 
 | ||||
| class Like extends Fragment { | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/Query/Limit.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/Query/Limit.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query; | ||||
| 
 | ||||
| class Limit extends Fragment { | ||||
| 
 | ||||
|     public int $order = 80; | ||||
| 
 | ||||
|     protected int $limit = 0; | ||||
| 
 | ||||
|     public function set($limit) : self | ||||
|     { | ||||
|         $this->limit = $limit; | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function render() : string | ||||
|     { | ||||
|         return $this->renderSegments([ | ||||
|             'LIMIT', $this->limit, | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								src/Query/MySQL/Replace.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/Query/MySQL/Replace.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query\MySQL; | ||||
| 
 | ||||
| class Replace extends \Ulmus\Query\Fragment { | ||||
| 
 | ||||
|     public int $order = -1000; | ||||
| 
 | ||||
|     public bool $extended = false; | ||||
| 
 | ||||
|     public function render() : string | ||||
|     { | ||||
|         return $this->renderSegments([ | ||||
|             "EXPLAIN", $this->extended ? "EXTENDED" : "" | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/Query/Offset.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/Query/Offset.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query; | ||||
| 
 | ||||
| class Offset extends Fragment { | ||||
| 
 | ||||
|     public int $order = 81; | ||||
| 
 | ||||
|     protected int $offset = 0; | ||||
| 
 | ||||
|     public function set($offset) : self | ||||
|     { | ||||
|         $this->offset = $offset; | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function render() : string | ||||
|     { | ||||
|         return $this->renderSegments([ | ||||
|             'OFFSET', $this->offset, | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										34
									
								
								src/Query/OrderBy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/Query/OrderBy.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query; | ||||
| 
 | ||||
| class OrderBy extends Fragment { | ||||
|     public int $order = 70; | ||||
| 
 | ||||
|     public array $orderBy = []; | ||||
| 
 | ||||
|     public function set(array $order) : self | ||||
|     { | ||||
|         $this->orderBy = $order; | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function add(string $field, ? string $direction = null) : self | ||||
|     { | ||||
|         $this->orderBy[] = [ $field, $direction ]; | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function render() : string | ||||
|     { | ||||
|         $list = array_map(function($item) { | ||||
|             list($field, $direction) = $item; | ||||
|             return $field . ( $direction ? " $direction" : "" ); | ||||
|         }, $this->orderBy); | ||||
| 
 | ||||
|         return $this->renderSegments([ | ||||
|             'ORDER BY', implode(", ", $list) | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										44
									
								
								src/Query/Select.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/Query/Select.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query; | ||||
| 
 | ||||
| class Select extends Fragment { | ||||
| 
 | ||||
|     public int $order = -100; | ||||
| 
 | ||||
|     public bool $distinct = false; | ||||
| 
 | ||||
|     public bool $union = false; | ||||
| 
 | ||||
|     public bool $top = false; | ||||
| 
 | ||||
|     protected $fields = []; | ||||
| 
 | ||||
|     public function set($fields) : self | ||||
|     { | ||||
|         $this->fields = is_array($fields) ? $fields : [ $fields ]; | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function add($fields) : self | ||||
|     { | ||||
|         if ( is_array($fields) ) { | ||||
|             $this->fields = array_merge($this->fields, $fields); | ||||
|         } | ||||
|         else { | ||||
|             $this->fields[] = $fields; | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function render() : string | ||||
|     { | ||||
|         return $this->renderSegments([ | ||||
|             ( $this->union ? 'UNION' : false ), | ||||
|             'SELECT', | ||||
|             ( $this->top ? 'TOP' : false ), | ||||
|             implode(', ', $this->fields) | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										151
									
								
								src/Query/Where.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								src/Query/Where.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,151 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus\Query; | ||||
| 
 | ||||
| use Ulmus\QueryBuilder; | ||||
| 
 | ||||
| use Ulmus\Common\EntityField, | ||||
|     Ulmus\Common\Sql; | ||||
| 
 | ||||
| class Where extends Fragment { | ||||
|     const OPERATOR_LIKE = "LIKE"; | ||||
|     const OPERATOR_EQUAL = "="; | ||||
|     const OPERATOR_NOT_EQUAL = "<>"; | ||||
|     const CONDITION_AND = "AND"; | ||||
|     const CONDITION_OR  = "OR"; | ||||
|     const CONDITION_NOT = "NOT"; | ||||
|     const COMPARISON_IN = "IN"; | ||||
|     const COMPARISON_IS = "IS"; | ||||
|     const COMPARISON_NULL = "NULL"; | ||||
| 
 | ||||
|     public int $order = 50; | ||||
| 
 | ||||
|     public array $conditionList; | ||||
| 
 | ||||
|     public QueryBuilder $queryBuilder; | ||||
| 
 | ||||
|     public ? Where $parent = null; | ||||
| 
 | ||||
|     public string $condition = self::CONDITION_AND; | ||||
| 
 | ||||
|     public function __construct(? QueryBuilder $queryBuilder, $condition = self::CONDITION_AND) | ||||
|     { | ||||
|         $this->queryBuilder = $queryBuilder; | ||||
|         $this->condition = $condition; | ||||
|         $this->parent = $queryBuilder->where ?? null; | ||||
|     } | ||||
| 
 | ||||
|     public function add($field, $value, string $operator, string $condition, bool $not = false) : self | ||||
|     { | ||||
|         $this->conditionList[] = [ | ||||
|             $field, | ||||
|             $value, | ||||
|             $operator ?: $this->queryBuilder->conditionOperator, | ||||
|             $condition, | ||||
|             $not | ||||
|         ]; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function render() : string | ||||
|     { | ||||
|         $stack = []; | ||||
| 
 | ||||
|         foreach ($this->conditionList ?? [] as $key => $item) { | ||||
|             if ( $item instanceof Where ) { | ||||
|                 if ( $item->conditionList ?? false ) { | ||||
|                     $stack[] = ( $key !== 0 ? "{$item->condition} " : "" ) . "(" . $item->render() . ")"; | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 list($field, $value, $operator, $condition, $not) = $item; | ||||
|                 $stack[] = $latest = $this->whereCondition($field, $value, $operator, $key !== 0 ? $condition : "", $not); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $this->renderSegments([ | ||||
|             ! $this->parent ? "WHERE" : "", | ||||
|             implode(" ", $stack) | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     protected function whereCondition($field, $value, string $operator = self::OPERATOR_EQUAL, string $condition = self::CONDITION_AND, bool $not = false) { | ||||
|         return new class($this->queryBuilder, $field, $value, $operator, $condition, $not) { | ||||
| 
 | ||||
|             public $value; | ||||
|             public bool $not = false; | ||||
|             public string $field; | ||||
|             public string $operator; | ||||
|             public string $condition; | ||||
|             public QueryBuilder $queryBuilder; | ||||
| 
 | ||||
|             protected string $content = ""; | ||||
| 
 | ||||
|             public function __construct(QueryBuilder $queryBuilder, string $field, $value, string $operator, string $condition, bool $not) { | ||||
|                 $this->queryBuilder = $queryBuilder; | ||||
|                 $this->field = $field; | ||||
|                 $this->value = $value; | ||||
|                 $this->condition = $condition; | ||||
|                 $this->operator = $operator; | ||||
|                 $this->not = $not; | ||||
|             } | ||||
| 
 | ||||
|             public function render() : string | ||||
|             { | ||||
|                 $value = $this->value(); | ||||
| 
 | ||||
|                 return $this->content ?: $this->content = implode(" ", array_filter([ | ||||
|                     $this->condition, | ||||
|                     $this->not ? Where::CONDITION_NOT : "", | ||||
|                     $this->field, | ||||
|                     $this->operator(), | ||||
|                     $value, | ||||
|                 ])); | ||||
|             } | ||||
| 
 | ||||
|             protected function operator() : string | ||||
|             { | ||||
|                 if ( is_array($this->value) ) { | ||||
|                     return (in_array($this->operator, [ '!=', '<>' ]) ? Where::CONDITION_NOT . " " : "") . Where::COMPARISON_IN; | ||||
|                 } | ||||
| 
 | ||||
|                 return  $this->operator; | ||||
|             } | ||||
| 
 | ||||
|             protected function value() | ||||
|             { | ||||
|                 if ( is_array($this->value) ) { | ||||
|                     $stack = []; | ||||
| 
 | ||||
|                     foreach($this->value as $item) { | ||||
|                         $stack[] = $this->filterValue($item); | ||||
|                     } | ||||
| 
 | ||||
|                     return "(" . implode(", ", $stack) . ")"; | ||||
|                 } | ||||
| 
 | ||||
|                 return $this->filterValue($this->value); | ||||
|             } | ||||
| 
 | ||||
|             protected function filterValue($value) | ||||
|             { | ||||
|                 if ( $value === null ) { | ||||
|                     $this->operator = in_array($this->operator, [ '!=', '<>' ]) ? Where::COMPARISON_IS . " " . Where::CONDITION_NOT : Where::COMPARISON_IS; | ||||
|                     return Where::COMPARISON_NULL; | ||||
|                 } | ||||
|                 elseif ( is_object($value) && ( $value instanceof EntityField ) ) { | ||||
|                     return $value->name(); | ||||
|                 } | ||||
|                 else { | ||||
|                     return $this->queryBuilder->addParameter($this->value); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             public function __toString() : string | ||||
|             { | ||||
|                 return $this->render(); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										182
									
								
								src/QueryBuilder.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								src/QueryBuilder.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,182 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus; | ||||
| 
 | ||||
| 
 | ||||
| class QueryBuilder | ||||
| { | ||||
| 
 | ||||
|     public Query\Where $where; | ||||
| 
 | ||||
|     /** | ||||
|      * Those are the parameters we are going to bind to PDO. | ||||
|      */ | ||||
|     public array $parameters = []; | ||||
| 
 | ||||
|     public string $conditionOperator = Query\Where::CONDITION_AND; | ||||
| 
 | ||||
|     public string $entityClass; | ||||
| 
 | ||||
|     protected int $parameterIndex = 0; | ||||
| 
 | ||||
|     protected array $queryStack = []; | ||||
| 
 | ||||
|     public function __construct($entityClass = "") { | ||||
|         $this->entityClass = $entityClass; | ||||
|     } | ||||
| 
 | ||||
|     public function select($field) : self | ||||
|     { | ||||
|         if ( $select = $this->has(Query\Select::class) ) { | ||||
|             $select->add($field); | ||||
|         } | ||||
|         else { | ||||
|             $select = new Query\Select(); | ||||
|             $select->set($field); | ||||
|             $this->push($select); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function from($table, $alias = null, $database = null) : self | ||||
|     { | ||||
|         if ( $database ) { | ||||
|             $table = "`$database`.".$table; | ||||
|         } | ||||
| 
 | ||||
|         if ( $from = $this->has(Query\From::class) ) { | ||||
|             $from->add($alias ? [ $alias => $table ] : $table); | ||||
|         } | ||||
|         else { | ||||
|             $from = new Query\From(); | ||||
|             $this->push($from); | ||||
|             $from->set($alias ? [ $alias => $table ] : $table); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function open(string $condition = Query\Where::CONDITION_AND) : self | ||||
|     { | ||||
|         if ( false !== ($this->where ?? false) ) { | ||||
|             $this->where->conditionList[] = $new = new Query\Where($this, $condition); | ||||
|             $this->where = $new; | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function close() : self | ||||
|     { | ||||
|         if ( false !== ($this->where ?? false) && $this->where->parent ) { | ||||
|             $this->where = $this->where->parent; | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function where($field, $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self | ||||
|     { | ||||
|         if ( $this->where ?? false ) { | ||||
|             $where = $this->where; | ||||
|         } | ||||
|         elseif ( false === $where = $this->has(Query\Where::class) ) { | ||||
|             $this->where = $where = new Query\Where($this); | ||||
|             $this->push($where); | ||||
|         } | ||||
| 
 | ||||
|         $this->conditionOperator = $operator; | ||||
|         $where->add($field, $value, $operator, $condition, $not); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function notWhere($field, $value, string $operator = Query\Where::CONDITION_AND) : self | ||||
|     { | ||||
|         return $this->where($field, $value, $operator, true); | ||||
|     } | ||||
| 
 | ||||
|     public function groupBy() : self | ||||
|     { | ||||
|         //$this->queryBuilder->groupBy();
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function limit(int $value) : self | ||||
|     { | ||||
|         if ( false === $limit = $this->has(Query\Limit::class) ) { | ||||
|             $limit = new Query\Limit(); | ||||
|             $this->push($limit); | ||||
|         } | ||||
| 
 | ||||
|         $limit->set($value); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function offset(int $value) : self | ||||
|     { | ||||
|         if ( false === $offset = $this->has(Query\Offset::class) ) { | ||||
|             $offset = new Query\Offset(); | ||||
|             $this->push($offset); | ||||
|         } | ||||
| 
 | ||||
|         $offset->set($value); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function orderBy(string $field, ? string $direction = null) : self | ||||
|     { | ||||
|         if ( false === $orderBy = $this->has(Query\OrderBy::class) ) { | ||||
|             $orderBy = new Query\OrderBy(); | ||||
|             $this->push($orderBy); | ||||
|         } | ||||
| 
 | ||||
|         $orderBy->add($field, $direction); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function push(Query\Fragment $queryFragment) : self | ||||
|     { | ||||
|         $this->queryStack[] = $queryFragment; | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function render() : string | ||||
|     { | ||||
|         $sql = []; | ||||
| 
 | ||||
|         usort($this->queryStack, function($q1, $q2) { | ||||
|             return $q1->order <=> $q2->order; | ||||
|         }); | ||||
| 
 | ||||
|         foreach($this->queryStack as $fragment) { | ||||
|             $sql[] = $fragment->render(); | ||||
|         } | ||||
| 
 | ||||
|         return implode(" ", $sql); | ||||
|     } | ||||
| 
 | ||||
|     public function has($class) { | ||||
|         foreach($this->queryStack as $item) { | ||||
|             if ( get_class($item) === $class ) { | ||||
|                 return $item; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     public function __toString() : string | ||||
|     { | ||||
|         return $this->render(); | ||||
|     } | ||||
| 
 | ||||
|     public function addParameter($value, $key = null) { | ||||
|         if ( $key === null ) { | ||||
|             $key = ":p" . $this->parameterIndex++; | ||||
|         } | ||||
| 
 | ||||
|         $this->parameters[$key] = $value; | ||||
|         return $key; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										254
									
								
								src/Repository.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										254
									
								
								src/Repository.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,254 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus; | ||||
| 
 | ||||
| use Ulmus\Common\EntityResolver; | ||||
| 
 | ||||
| class Repository | ||||
| { | ||||
|     const DEFAULT_ALIAS = "this"; | ||||
| 
 | ||||
|     protected ? ConnectionAdapter $adapter; | ||||
| 
 | ||||
|     protected QueryBuilder $queryBuilder; | ||||
| 
 | ||||
|     protected EntityResolver $entityResolver; | ||||
| 
 | ||||
|     public string $alias; | ||||
| 
 | ||||
|     public string $entityClass; | ||||
| 
 | ||||
|     public function __construct(string $entity, string $alias = self::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) { | ||||
|         $this->entityClass = $entity; | ||||
|         $this->alias = $alias; | ||||
|         $this->adapter = $adapter; | ||||
|         $this->queryBuilder = new QueryBuilder(); | ||||
|         $this->entityResolver = Ulmus::resolveEntity($entity); | ||||
|     } | ||||
| 
 | ||||
|     public function loadOne() : EntityCollection | ||||
|     { | ||||
|         return $this->limit(1)->collectionFromQuery(); | ||||
|     } | ||||
| 
 | ||||
|     public function loadAll() : EntityCollection | ||||
|     { | ||||
|         return $this->collectionFromQuery(); | ||||
|     } | ||||
| 
 | ||||
|     public function loadFromPk($value) : EntityCollection | ||||
|     { | ||||
|         return $this->where('id', $value)->loadOne(); | ||||
|     } | ||||
| 
 | ||||
|     public function loadFromField($field, $value) : EntityCollection | ||||
|     { | ||||
|         return $this->where($field, $value)->collectionFromQuery(); | ||||
|     } | ||||
| 
 | ||||
|     public function yieldAll() : \Generator | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function select($fields) : self | ||||
|     { | ||||
|         $this->queryBuilder->select($fields); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function from($table) : self | ||||
|     { | ||||
|         foreach((array) $table as $alias => $table) { | ||||
|             $this->queryBuilder->from($table, is_numeric($alias) ? null : $alias); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function join(string $type, $table, $field, $value) : self | ||||
|     { | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function open(string $condition = Query\Where::CONDITION_AND) : self | ||||
|     { | ||||
|         $this->queryBuilder->open($condition); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function orOpen() : self | ||||
|     { | ||||
|         return $this->open(Query\Where::CONDITION_OR); | ||||
|     } | ||||
| 
 | ||||
|     public function close() : self | ||||
|     { | ||||
|         $this->queryBuilder->close(); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function where($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self | ||||
|     { | ||||
|         $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_AND); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function and($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self | ||||
|     { | ||||
|         return $this->where($field, $value, $operator); | ||||
|     } | ||||
| 
 | ||||
|     public function or($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self | ||||
|     { | ||||
|         $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_OR); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function notWhere(array $condition) : self | ||||
|     { | ||||
|         $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_AND, true); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function orNot($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self | ||||
|     { | ||||
|         $this->queryBuilder->notWhere($condition, Query\Where::CONDITION_OR, true); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function having() : self | ||||
|     { | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function notHaving() : self | ||||
|     { | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function in($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self | ||||
|     { | ||||
|         $this->queryBuilder->where($field, $value, $operator); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function orIn($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self | ||||
|     { | ||||
|         $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_OR); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function notIn($field, $value) : self | ||||
|     { | ||||
|         $this->queryBuilder->where($field, $value, Query\Where::OPERATOR_NOT_EQUAL); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function orNotIn($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self | ||||
|     { | ||||
|         return $this->orNot($field, $value, Query\Where::OPERATOR_NOT_EQUAL, Query\Where::CONDITION_OR); | ||||
|     } | ||||
| 
 | ||||
|     public function like($field, $value) : self | ||||
|     { | ||||
|         $this->queryBuilder->where($field, $value, Query\Where::OPERATOR_LIKE, Query\Where::CONDITION_AND); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function notLike($field, $value) : self | ||||
|     { | ||||
|         $this->queryBuilder->where($field, $value, Query\Where::OPERATOR_LIKE, Query\Where::CONDITION_AND, true); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function match() : self | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function notMatch() : self | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function between() : self | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function notBetween() : self | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function groupBy() : self | ||||
|     { | ||||
|         #$this->queryBuilder->groupBy();
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function orderBy(string $field, ? string $direction = null) : self | ||||
|     { | ||||
|         $this->queryBuilder->orderBy($field, $direction); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function limit(int $value) : self | ||||
|     { | ||||
|         $this->queryBuilder->limit($value); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function offset(int $value) : self | ||||
|     { | ||||
|         $this->queryBuilder->offset($value); | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function commit() : self | ||||
|     { | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function rollback() : self | ||||
|     { | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     protected function collectionFromQuery() : EntityCollection | ||||
|     { | ||||
|         $class = $this->entityClass; | ||||
| 
 | ||||
|         $entityCollection = new EntityCollection(); | ||||
| 
 | ||||
|         foreach(Ulmus::iterateQueryBuilder($this->selectSqlQuery()->queryBuilder) as $entityData) { | ||||
|             $entityCollection->append( ( new $class() )->entityFillFromDataset($entityData) ); | ||||
|         } | ||||
| 
 | ||||
|         return $entityCollection; | ||||
|     } | ||||
| 
 | ||||
|     protected function selectSqlQuery() : self | ||||
|     { | ||||
|         if ( ! $this->queryBuilder->has(Query\Select::class) ) { | ||||
|             $this->select("{$this->alias}.*"); | ||||
|         } | ||||
| 
 | ||||
|         if ( ! $this->queryBuilder->has(Query\From::class) ) { | ||||
|             $this->from([ $this->alias => $this->entityResolver->tableName() ]); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     protected function fromRow($row) : self | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     protected function fromCollection($rows) : self | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
							
								
								
									
										53
									
								
								src/Ulmus.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/Ulmus.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ulmus; | ||||
| 
 | ||||
| use Generator; | ||||
| 
 | ||||
| abstract class Ulmus | ||||
| { | ||||
|     public static string $repositoryClass = "\\Ulmus\\Repository"; | ||||
| 
 | ||||
|     public static string $queryBuilderClass = "\\Ulmus\\QueryBuilder"; | ||||
| 
 | ||||
|     public static ConnectionAdapter $defaultAdapter; | ||||
| 
 | ||||
|     public static array $resolved = []; | ||||
| 
 | ||||
|     protected static function fetchQueryBuilder(QueryBuilder $queryBuilder, ?ConnectionAdapter $adapter = null) : array | ||||
|     { | ||||
|         $sql = $queryBuilder->render(); | ||||
|         return ( $adapter ?: static::$defaultAdapter )->pdo->select($sql, $queryBuilder->parameters ?? [])->fetchAll(); | ||||
|     } | ||||
| 
 | ||||
|     public static function iterateQueryBuilder(QueryBuilder $queryBuilder, ?ConnectionAdapter $adapter = null) : Generator | ||||
|     { | ||||
|         $sql = $queryBuilder->render(); | ||||
|         $statement = ( $adapter ?: static::$defaultAdapter )->pdo->select($sql, $queryBuilder->parameters ?? []); | ||||
| 
 | ||||
|         while ( $row = $statement->fetch() ) { | ||||
|             yield $row; | ||||
|         } | ||||
| 
 | ||||
|         $statement->closeCursor(); | ||||
| 
 | ||||
|         return [ | ||||
|             'count' => $statement->rowCount(), | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public static function resolveEntity(string $entityClass) : Common\EntityResolver | ||||
|     { | ||||
|         return static::$resolved[$entityClass] ?? static::$resolved[$entityClass] = new Common\EntityResolver($entityClass); | ||||
|     } | ||||
| 
 | ||||
|     public static function repository(...$arguments) : Repository | ||||
|     { | ||||
|         return new static::$repositoryClass(...$arguments); | ||||
|     } | ||||
| 
 | ||||
|     public static function queryBuilder(...$arguments) : QueryBuilder | ||||
|     { | ||||
|         return new static::$queryBuilderClass(...$arguments); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user