+ * @link http://lightql.na2axl.tk/docs/api/LightQL/Entities/EntitiesCollection
+ */
+class EntitiesCollection implements \ArrayAccess, \SeekableIterator, \Countable
+{
+ /**
+ * The list of entities
+ *
+ * @var IEntity[]
+ */
+ private $_entities;
+
+ /**
+ * The current position in the iterator
+ *
+ * @var int
+ */
+ private $_position = 0;
+
+ public function __construct(array $entities)
+ {
+ $this->_entities = $entities;
+ }
+
+ public function order(...$params) : self
+ {
+ if (count($params) === 1) {
+ if (is_callable($params[0])) {
+ $this->_sort($params[0]);
+ } elseif (is_string($params[0])) {
+ $this->_sort(function($current, $next) use ($params) {
+ return ($current->get($params[0]) > $next->get($params[0]));
+ });
+ }
+ } elseif (count($params) === 2 && is_string($params[0]) && is_string($params[1])) {
+ if (strtolower($params[1]) !== 'asc' && strtolower($params[1]) !== 'desc') {
+ throw new Exception("The second parameter must be 'asc' or 'desc'");
+ }
+
+ $this->_sort(function($current, $next) use ($params) {
+ if (strtolower($params[1]) === 'asc') {
+ return ($current->get($params[0]) > $next->get($params[0]));
+ } elseif (strtolower($params[1]) === 'desc') {
+ return ($current->get($params[0]) < $next->get($params[0]));
+ }
+ });
+ } else {
+ throw new ArgumentCountError();
+ }
+
+ return $this;
+ }
+
+ public function filter(callable $func) : self
+ {
+ $result = array();
+
+ foreach ($this->_entities as $entity) {
+ if ($func($entity) === true) {
+ array_push($result, $entity);
+ }
+ }
+
+ $this->_entities = array_values($result);
+
+ return $this;
+ }
+
+ public function toArray()
+ {
+ return $this->_entities;
+ }
+
+ /**
+ * Whether a offset exists
+ * @link http://php.net/manual/en/arrayaccess.offsetexists.php
+ * @param mixed $offset
+ * An offset to check for.
+ *
+ * @return bool true on success or false on failure.
+ *
+ *
+ * The return value will be casted to boolean if non-boolean was returned.
+ * @since 5.0.0
+ */
+ public function offsetExists($offset)
+ {
+ return isset($this->_entities[$offset]);
+ }
+
+ /**
+ * Offset to retrieve
+ * @link http://php.net/manual/en/arrayaccess.offsetget.php
+ * @param mixed $offset
+ * The offset to retrieve.
+ *
+ * @return mixed Can return all value types.
+ * @since 5.0.0
+ */
+ public function offsetGet($offset)
+ {
+ return $this->offsetExists($offset) ? $this->_entities[$offset] : null;
+ }
+
+ /**
+ * Offset to set
+ * @link http://php.net/manual/en/arrayaccess.offsetset.php
+ * @param mixed $offset
+ * The offset to assign the value to.
+ *
+ * @param mixed $value
+ * The value to set.
+ *
+ * @return void
+ * @since 5.0.0
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->_entities[$offset] = $value;
+ }
+
+ /**
+ * Offset to unset
+ * @link http://php.net/manual/en/arrayaccess.offsetunset.php
+ * @param mixed $offset
+ * The offset to unset.
+ *
+ * @return void
+ * @since 5.0.0
+ */
+ public function offsetUnset($offset)
+ {
+ if ($this->offsetExists($offset)) {
+ unset($this->_entities[$offset]);
+ }
+ }
+
+ /**
+ * Return the current element
+ * @link http://php.net/manual/en/iterator.current.php
+ * @return mixed Can return any type.
+ * @since 5.0.0
+ */
+ public function current()
+ {
+ $values = array_values($this->_entities);
+
+ return $values[$this->_position];
+ }
+
+ /**
+ * Move forward to next element
+ * @link http://php.net/manual/en/iterator.next.php
+ * @return void Any returned value is ignored.
+ * @since 5.0.0
+ */
+ public function next()
+ {
+ $this->_position++;
+ }
+
+ /**
+ * Return the key of the current element
+ * @link http://php.net/manual/en/iterator.key.php
+ * @return mixed scalar on success, or null on failure.
+ * @since 5.0.0
+ */
+ public function key()
+ {
+ $keys = array_keys($this->_entities);
+
+ return $keys[$this->_position];
+ }
+
+ /**
+ * Checks if current position is valid
+ * @link http://php.net/manual/en/iterator.valid.php
+ * @return bool The return value will be casted to boolean and then evaluated.
+ * Returns true on success or false on failure.
+ * @since 5.0.0
+ */
+ public function valid()
+ {
+ return $this->_position < count($this->_entities);
+ }
+
+ /**
+ * Rewind the Iterator to the first element
+ * @link http://php.net/manual/en/iterator.rewind.php
+ * @return void Any returned value is ignored.
+ * @since 5.0.0
+ */
+ public function rewind()
+ {
+ $this->_position = 0;
+ }
+
+ /**
+ * Seeks to a position
+ * @link http://php.net/manual/en/seekableiterator.seek.php
+ * @param int $position
+ * The position to seek to.
+ *
+ * @return void
+ * @since 5.1.0
+ */
+ public function seek($position)
+ {
+ $this->_position = $position;
+ }
+
+ public function count()
+ {
+ return count($this->_entities);
+ }
+
+ private function _sort(callable $func)
+ {
+ for ($i = 0, $l = count($this->_entities); $i < $l - 1; $i++) {
+ for ($j = $i+1; $j < $l; $j++) {
+ if ($func($this->_entities[$i], $this->_entities[$j])) {
+ $t = $this->_entities[$i];
+ $this->_entities[$i] = $this->_entities[$j];
+ $this->_entities[$j] = $t;
+ }
+ }
+ }
+ }
+}
diff --git a/src/LightQL/Entities/Entity.php b/src/LightQL/Entities/Entity.php
index 94d6ebf..bf328b5 100644
--- a/src/LightQL/Entities/Entity.php
+++ b/src/LightQL/Entities/Entity.php
@@ -35,7 +35,6 @@
use ElementaryFramework\Annotations\Annotations;
use ElementaryFramework\Annotations\IAnnotation;
use ElementaryFramework\LightQL\Exceptions\EntityException;
-use ElementaryFramework\LightQL\Exceptions\AnnotationException;
/**
* Entity
@@ -67,6 +66,20 @@ abstract class Entity implements IEntity
*/
protected $raw = array();
+ /**
+ * The raw data with the value transformer applied.
+ *
+ * @var array
+ */
+ private $_polished = array();
+
+ /**
+ * The name of the table managed by the current entity.
+ *
+ * @var string
+ */
+ private $_table;
+
/**
* The reflection class of this entity.
*
@@ -87,7 +100,7 @@ abstract class Entity implements IEntity
* @param array $data The raw database data.
*
* @throws EntityException
- * @throws AnnotationException
+ * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
* @throws \ReflectionException
*/
public function __construct(array $data = array())
@@ -96,8 +109,10 @@ public function __construct(array $data = array())
throw new EntityException("Cannot create an entity without the @entity annotation.");
}
+ $this->_table = Annotations::ofClass($this, "@entity")[0]->table;
+
$this->_reflection = new \ReflectionClass($this);
- $properties = $this->_reflection->getProperties();
+ $properties = $this->_reflection->getProperties(T_PUBLIC);
$pkFound = false;
@@ -140,9 +155,20 @@ public function __construct(array $data = array())
*/
public function hydrate(array $data)
{
+ /** @var IValueTransformer $valueTransformer */
+ $valueTransformer = Entity::getValueTransformerOfEntity($this);
+
// Merge values
foreach ($data as $name => $value) {
+ // Save the raw value
$this->raw[$name] = $value;
+
+ // Save the polished value
+ if (is_string($name)) {
+ $this->_polished[$name] = $valueTransformer !== null
+ ? $valueTransformer->toEntityValue($this->_table, $name, $value)
+ : $value;
+ }
}
// Populate @column properties
@@ -154,7 +180,8 @@ public function hydrate(array $data)
} elseif (\is_null($this->{$property}) || $this->{$property} === null) {
$this->{$property} = $column->getDefault();
}
- } else {
+ } elseif (array_key_exists($column->getName(), $this->raw)) {
+ // TODO: Find a way to fill the property with the good value
$this->{$property} = null;
}
}
@@ -166,14 +193,10 @@ public function hydrate(array $data)
* @param string $column The table column name.
*
* @return mixed
+ * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
*/
public function get(string $column)
{
- // Try to get the raw value
- if ($this->_exists($column)) {
- return $this->raw[$column];
- }
-
// Try to get the property value
/** @var Column $c */
foreach ($this->_columns as $property => $c) {
@@ -192,12 +215,17 @@ public function get(string $column)
$referencedColumn = $this->_getMetadata($property, "@oneToOne", "referencedColumn");
return $this->{$property}->get($referencedColumn);
}
- } else {
+ } elseif (!$c->isManyToOne && $this->{$property} !== null) {
return $this->{$property};
}
}
}
+ // Try to get the raw value
+ if ($this->_exists($column)) {
+ return $this->_polished[$column];
+ }
+
// The value definitively doesn't exist
return null;
}
@@ -224,6 +252,16 @@ public function getColumns(): array
return $this->_columns;
}
+ /**
+ * Gets the entity as array.
+ *
+ * @return array
+ */
+ public function getRawEntity(): array
+ {
+ return $this->raw;
+ }
+
/**
* Checks if a property has the given annotation.
*
@@ -280,4 +318,52 @@ private function _exists(string $column): bool
{
return array_key_exists($column, $this->raw);
}
+
+ /**
+ * @param $entity
+ * @return IValueValidator|null
+ * @throws AnnotationException
+ * @throws EntityException
+ * @throws \ReflectionException
+ */
+ public static function getValueValidatorOfEntity($entity)
+ {
+ if (Annotations::classHasAnnotation($entity, "@validator")) {
+ $validatorAnnotation = Annotations::ofClass($entity, "@validator");
+
+ if (\is_subclass_of($validatorAnnotation[0]->validator, IValueValidator::class)) {
+ $validatorClass = new \ReflectionClass($validatorAnnotation[0]->validator);
+
+ return $validatorClass->newInstance();
+ } else {
+ throw new EntityException("The value validator of this entity doesn't implement the IValueValidator interface.");
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param $entity
+ * @return IValueTransformer|null
+ * @throws AnnotationException
+ * @throws EntityException
+ * @throws \ReflectionException
+ */
+ public static function getValueTransformerOfEntity($entity)
+ {
+ if (Annotations::classHasAnnotation($entity, "@transformer")) {
+ $transformerAnnotation = Annotations::ofClass($entity, "@transformer");
+
+ if (\is_subclass_of($transformerAnnotation[0]->transformer, IValueTransformer::class)) {
+ $transformerClass = new \ReflectionClass($transformerAnnotation[0]->transformer);
+
+ return $transformerClass->newInstance();
+ } else {
+ throw new EntityException("The value transformer of this entity doesn't implement the IValueTransformer interface.");
+ }
+ }
+
+ return null;
+ }
}
diff --git a/src/LightQL/Entities/EntityManager.php b/src/LightQL/Entities/EntityManager.php
index 3acd650..8e17f76 100644
--- a/src/LightQL/Entities/EntityManager.php
+++ b/src/LightQL/Entities/EntityManager.php
@@ -33,7 +33,10 @@
namespace ElementaryFramework\LightQL\Entities;
use ElementaryFramework\Annotations\Annotations;
+use ElementaryFramework\Annotations\Exceptions\AnnotationException;
use ElementaryFramework\LightQL\Exceptions\EntityException;
+use ElementaryFramework\LightQL\Exceptions\LightQLException;
+use ElementaryFramework\LightQL\Exceptions\ValueValidatorException;
use ElementaryFramework\LightQL\LightQL;
use ElementaryFramework\LightQL\Persistence\PersistenceUnit;
@@ -42,7 +45,6 @@
*
* Manage all entities, using one same persistence unit.
*
- * @final
* @category Entities
* @package LightQL
* @author Nana Axel
@@ -50,6 +52,13 @@
*/
final class EntityManager
{
+ /**
+ * The array of existing LightQL connections.
+ *
+ * @var array
+ */
+ private static $_connections = array();
+
/**
* The persistence unit of this entity
* manager.
@@ -71,23 +80,30 @@ final class EntityManager
*
* @param PersistenceUnit $persistenceUnit The persistence unit to use in this manager.
*
- * @throws \ElementaryFramework\LightQL\Exceptions\LightQLException
+ * @throws LightQLException
*/
public function __construct(PersistenceUnit $persistenceUnit)
{
// Save the persistence unit
$this->_persistenceUnit = $persistenceUnit;
- // Create a LightQL instance
- $this->_lightql = new LightQL(
- array(
- "dbms" => $this->_persistenceUnit->getDbms(),
- "database" => $this->_persistenceUnit->getDatabase(),
- "hostname" => $this->_persistenceUnit->getHostname(),
- "username" => $this->_persistenceUnit->getUsername(),
- "password" => $this->_persistenceUnit->getPassword()
- )
- );
+ if (!array_key_exists($persistenceUnit->getKey(), static::$_connections)) {
+ // Create a LightQL instance
+ $this->_lightql = new LightQL(
+ array(
+ "dbms" => $this->_persistenceUnit->getDbms(),
+ "database" => $this->_persistenceUnit->getDatabase(),
+ "hostname" => $this->_persistenceUnit->getHostname(),
+ "username" => $this->_persistenceUnit->getUsername(),
+ "password" => $this->_persistenceUnit->getPassword(),
+ "port" => $this->_persistenceUnit->getPort()
+ )
+ );
+
+ static::$_connections[$persistenceUnit->getKey()] = $this->_lightql;
+ } else {
+ $this->_lightql = static::$_connections[$persistenceUnit->getKey()];
+ }
}
/**
@@ -96,13 +112,13 @@ public function __construct(PersistenceUnit $persistenceUnit)
* @param string $entityClass The class name of the entity to find.
* @param mixed $id The value of the primary key.
*
- * @return array Raw data from database.
+ * @return array|null Raw data from database or null if no data.
*
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\LightQLException
+ * @throws AnnotationException
+ * @throws LightQLException
* @throws \ReflectionException
*/
- public function find(string $entityClass, $id): array
+ public function find(string $entityClass, $id): ?array
{
$entityAnnotation = Annotations::ofClass($entityClass, "@entity");
@@ -114,20 +130,20 @@ public function find(string $entityClass, $id): array
if ($id instanceof IPrimaryKey) {
$pkClass = new \ReflectionClass($id);
- $properties = $pkClass->getProperties();
+ $properties = $pkClass->getProperties(T_PUBLIC);
/** @var \ReflectionProperty $property */
foreach ($properties as $property) {
if (Annotations::propertyHasAnnotation($id, $property->getName(), "@id") && Annotations::propertyHasAnnotation($id, $property->getName(), "@column")) {
$name = Annotations::ofProperty($id, $property->getName(), "@column")[0]->name;
- $where[$name] = $this->_lightql->quote($id->{$property->getName()});
+ $where[$name] = $this->_lightql->parseValue($id->{$property->getName()});
}
}
} else {
foreach ($columns as $property => $column) {
if (count($where) === 0) {
if ($column->isPrimaryKey) {
- $where[$column->getName()] = $this->_lightql->quote($id);
+ $where[$column->getName()] = $this->_lightql->parseValue($id);
}
} else break;
}
@@ -144,10 +160,14 @@ public function find(string $entityClass, $id): array
/**
* Persists an entity into the database.
*
- * @param Entity $entity The entity to create.
+ * @param Entity &$entity The entity to create.
+ *
+ * @return array
*
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\EntityException
+ * @throws EntityException
+ * @throws ValueValidatorException
+ * @throws AnnotationException
+ * @throws \ReflectionException
*/
public function persist(Entity &$entity)
{
@@ -158,6 +178,8 @@ public function persist(Entity &$entity)
$autoIncrementProperty = null;
$idProperty = null;
+ $valueValidator = Entity::getValueValidatorOfEntity($entity);
+ $valueTransformer = Entity::getValueTransformerOfEntity($entity);
/** @var Column $column */
foreach ($columns as $property => $column) {
@@ -170,6 +192,19 @@ public function persist(Entity &$entity)
}
}
+ if ($idProperty === null) {
+ $entityReflection = new \ReflectionClass($entity);
+ $entityProperties = $entityReflection->getProperties(T_PUBLIC);
+
+ /** @var \ReflectionProperty $property */
+ foreach ($entityProperties as $property) {
+ if (Annotations::propertyHasAnnotation($entity, $property->name, "@id")) {
+ $idProperty = $property->name;
+ break;
+ }
+ }
+ }
+
if ($idProperty !== null && ($autoIncrementProperty === null || $autoIncrementProperty !== $idProperty)) {
// We have a non auto incremented primary key...
// Check if the value is null or not set
@@ -203,35 +238,95 @@ public function persist(Entity &$entity)
}
}
+ /** @var Column $column */
foreach ($columns as $property => $column) {
- $fieldAndValues[$column->getName()] = $this->_lightql->quote($entity->get($column->getName()));
+ if ($column->isManyToOne) {
+ continue;
+ }
+
+ $value = $this->_lightql->parseValue($entity->get($column->getName()));
+
+ if ($valueValidator !== null && !$valueValidator->validate($entity, $property)) {
+ throw new ValueValidatorException($property);
+ }
+
+ if ($valueTransformer !== null) {
+ $value = $this->_lightql->parseValue($valueTransformer->toDatabaseValue($entity, $property));
+ }
+
+ $fieldAndValues[$column->getName()] = $value;
+ }
+
+ if (Annotations::classHasAnnotation($entity, "@pkClass")) {
+ $pkClassName = Annotations::ofClass($entity, "@pkClass")[0]->name;
+ $pkClassReflection = new \ReflectionClass($pkClassName);
+ $pkClassProperties = $pkClassReflection->getProperties(T_PUBLIC);
+
+ /** @var \ReflectionProperty $property */
+ foreach ($pkClassProperties as $property) {
+ if (Annotations::propertyHasAnnotation($pkClassName, $property->name, "@column")) {
+ $columnAnnotations = Annotations::ofProperty($pkClassName, $property->name, "@column");
+ $fieldAndValues[$columnAnnotations[0]->name] = $this->_lightql->parseValue($entity->{$idProperty}->{$property->name});
+ }
+ }
+ }
+
+ $inTransaction = $this->_lightql->inTransaction();
+
+ if (!$inTransaction) {
+ $this->_lightql->beginTransaction();
}
- $this->_lightql->beginTransaction();
try {
$this->_lightql
->from($entityAnnotation[0]->table)
->insert($fieldAndValues);
- if ($autoIncrementProperty !== null) {
- $entity->$autoIncrementProperty = $this->_lightql->lastInsertID();
+ if (!$inTransaction) {
+ $this->_lightql->commit();
}
-
- $this->_lightql->commit();
} catch (\Exception $e) {
- $this->_lightql->rollback();
+ if (!$inTransaction) {
+ $this->_lightql->rollback();
+ }
- throw new EntityException($e->getMessage());
+ throw new EntityException("Unable to persist the entity. See internal exception to learn more.", 0, $e);
}
+
+ $where = array();
+ if ($autoIncrementProperty !== null) {
+ $where[$columns[$autoIncrementProperty]->getName()] = $this->_lightql->lastInsertID();
+ } elseif ($entity->{$idProperty} instanceof IPrimaryKey) {
+ $pkClassName = Annotations::ofClass($entity, "@pkClass")[0]->name;
+ $pkClassReflection = new \ReflectionClass($pkClassName);
+ $pkClassProperties = $pkClassReflection->getProperties(T_PUBLIC);
+
+ /** @var \ReflectionProperty $property */
+ foreach ($pkClassProperties as $property) {
+ if (Annotations::propertyHasAnnotation($pkClassName, $property->name, "@column")) {
+ $columnAnnotations = Annotations::ofProperty($pkClassName, $property->name, "@column");
+ $where[$columnAnnotations[0]->name] = $this->_lightql->parseValue($entity->{$idProperty}->{$property->name});
+ }
+ }
+ } else {
+ $where[$columns[$idProperty]->getName()] = $this->_lightql->parseValue($entity->{$idProperty});
+ }
+
+ return $this->_lightql
+ ->from($entityAnnotation[0]->table)
+ ->where($where)
+ ->selectFirst();
}
/**
* Merges the entity in the database with the given one.
*
- * @param Entity $entity The entity to edit.
+ * @param Entity &$entity The entity to edit.
*
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\EntityException
+ * @throws EntityException
+ * @throws ValueValidatorException
+ * @throws AnnotationException
+ * @throws \ReflectionException
*/
public function merge(Entity &$entity)
{
@@ -239,46 +334,72 @@ public function merge(Entity &$entity)
$columns = $entity->getColumns();
$fieldAndValues = array();
+ $valueValidator = Entity::getValueValidatorOfEntity($entity);
+ $valueTransformer = Entity::getValueTransformerOfEntity($entity);
$where = array();
$entityReflection = new \ReflectionClass($entity);
- $entityProperties = $entityReflection->getProperties();
+ $entityProperties = $entityReflection->getProperties(T_PUBLIC);
/** @var \ReflectionProperty $property */
foreach ($entityProperties as $property) {
$id = $entity->{$property->getName()};
if ($id instanceof IPrimaryKey) {
$propertyReflection = new \ReflectionClass($id);
- $propertyProperties = $propertyReflection->getProperties();
+ $propertyProperties = $propertyReflection->getProperties(T_PUBLIC);
foreach ($propertyProperties as $key) {
$name = Annotations::ofProperty($id, $key->getName(), "@column")[0]->name;
- $where[$name] = $this->_lightql->quote($id->{$key->getName()});
+ $where[$name] = $this->_lightql->parseValue($id->{$key->getName()});
}
break;
}
}
+ /** @var Column $column */
foreach ($columns as $property => $column) {
- $fieldAndValues[$column->getName()] = $this->_lightql->quote($entity->get($column->getName()));
+ if ($column->isManyToOne) {
+ continue;
+ }
+
+ $value = $this->_lightql->parseValue($entity->get($column->getName()));
+
+ if ($valueValidator !== null && !$valueValidator->validate($entity, $property)) {
+ throw new ValueValidatorException($property);
+ }
+
+ if ($valueTransformer !== null) {
+ $value = $this->_lightql->parseValue($valueTransformer->toDatabaseValue($entity, $property));
+ }
+
+ $fieldAndValues[$column->getName()] = $value;
if ($column->isPrimaryKey) {
- $where[$column->getName()] = $this->_lightql->quote($entity->get($column->getName()));
+ $where[$column->getName()] = $this->_lightql->parseValue($entity->get($column->getName()));
}
}
- $this->_lightql->beginTransaction();
+ $inTransaction = $this->_lightql->inTransaction();
+
+ if (!$inTransaction) {
+ $this->_lightql->beginTransaction();
+ }
+
try {
$this->_lightql
->from($entityAnnotation[0]->table)
->where($where)
->update($fieldAndValues);
- $this->_lightql->commit();
+ if (!$inTransaction) {
+ $this->_lightql->commit();
+ }
} catch (\Exception $e) {
- $this->_lightql->rollback();
+ if (!$inTransaction) {
+ $this->_lightql->rollback();
+ }
throw new EntityException($e->getMessage());
}
@@ -287,34 +408,34 @@ public function merge(Entity &$entity)
/**
* Removes an entity from the database.
*
- * @param Entity $entity The entity to delete.
+ * @param Entity &$entity The entity to delete.
*
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\EntityException
+ * @throws EntityException
+ * @throws AnnotationException
+ * @throws \ReflectionException
*/
public function delete(Entity &$entity)
{
$entityAnnotation = Annotations::ofClass($entity, "@entity");
$columns = $entity->getColumns();
- $fieldAndValues = array();
$where = array();
$pk = array();
$entityReflection = new \ReflectionClass($entity);
- $entityProperties = $entityReflection->getProperties();
+ $entityProperties = $entityReflection->getProperties(T_PUBLIC);
/** @var \ReflectionProperty $property */
foreach ($entityProperties as $property) {
$id = $entity->{$property->getName()};
if ($id instanceof IPrimaryKey) {
$propertyReflection = new \ReflectionClass($id);
- $propertyProperties = $propertyReflection->getProperties();
+ $propertyProperties = $propertyReflection->getProperties(T_PUBLIC);
foreach ($propertyProperties as $key) {
$name = Annotations::ofProperty($id, $key->getName(), "@column")[0]->name;
- $where[$name] = $this->_lightql->quote($id->{$key->getName()});
+ $where[$name] = $this->_lightql->parseValue($id->{$key->getName()});
$pk[] = $property->getName();
}
@@ -324,12 +445,17 @@ public function delete(Entity &$entity)
foreach ($columns as $property => $column) {
if ($column->isPrimaryKey) {
- $where[$column->getName()] = $this->_lightql->quote($entity->get($column->getName()));
+ $where[$column->getName()] = $this->_lightql->parseValue($entity->get($column->getName()));
$pk[] = $property;
}
}
- $this->_lightql->beginTransaction();
+ $inTransaction = $this->_lightql->inTransaction();
+
+ if (!$inTransaction) {
+ $this->_lightql->beginTransaction();
+ }
+
try {
$this->_lightql
->from($entityAnnotation[0]->table)
@@ -338,13 +464,17 @@ public function delete(Entity &$entity)
if (count($pk) > 0) {
foreach ($pk as $item) {
- $entity->$item = null;
+ $entity->{$item} = null;
}
}
- $this->_lightql->commit();
+ if (!$inTransaction) {
+ $this->_lightql->commit();
+ }
} catch (\Exception $e) {
- $this->_lightql->rollback();
+ if (!$inTransaction) {
+ $this->_lightql->rollback();
+ }
throw new EntityException($e->getMessage());
}
diff --git a/src/LightQL/Entities/GenericEntity.php b/src/LightQL/Entities/GenericEntity.php
new file mode 100644
index 0000000..825b252
--- /dev/null
+++ b/src/LightQL/Entities/GenericEntity.php
@@ -0,0 +1,165 @@
+
+ * @copyright 2018 Aliens Group, Inc.
+ * @license MIT
+ * @version 1.0.0
+ * @link http://lightql.na2axl.tk
+ */
+
+namespace ElementaryFramework\LightQL\Entities;
+
+/**
+ * Generic Entity
+ *
+ * Represent an entity which can adapt to any table structure.
+ *
+ * @package ElementaryFramework\LightQL\Entities
+ * @author Nana Axel
+ * @link http://lightql.na2axl.tk/docs/api/LightQL/Entities/GenericEntity
+ */
+final class GenericEntity implements IEntity
+{
+ /**
+ * The name of the table held by this entity.
+ *
+ * @var string
+ */
+ private $_name;
+
+ /**
+ * The name of the column defined as the primary key of the table, if any.
+ *
+ * @var string
+ */
+ private $_pk;
+
+ /**
+ * The collection of key-value pairs, in which keys represent columns names.
+ *
+ * @var array
+ */
+ private $_data;
+
+ /**
+ * GenericEntity constructor.
+ *
+ * @param string $name The name of the table managed by this GenericEntity.
+ * @param null|string $pk The name of the column defined as the primary key of the table, if any.
+ * @param array $data The initial values of columns in this GenericEntity.
+ */
+ public function __construct(string $name, string $pk, array $data = array())
+ {
+ $this->_name = $name;
+ $this->_pk = $pk;
+
+ $this->hydrate($data);
+ }
+
+ /**
+ * Populates data in the entity.
+ *
+ * @param array $data The raw database data.
+ */
+ public function hydrate(array $data)
+ {
+ foreach ($data as $column => $value) {
+ $this->set($column, $value);
+ }
+ }
+
+ /**
+ * Sets the raw value of a table column.
+ *
+ * @param string $column The table column name.
+ * @param mixed $value The table column value.
+ */
+ public function set(string $column, $value)
+ {
+ $this->_data[$column] = $value;
+ }
+
+ /**
+ * @param string $name The column's name.
+ *
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * @param string $name The column's name.
+ * @param mixed $value The value to assign.
+ */
+ public function __set($name, $value)
+ {
+ $this->set($name, $value);
+ }
+
+ /**
+ * Gets the raw value of a table column.
+ *
+ * @param string $column The table column name.
+ *
+ * @return mixed
+ */
+ public function get(string $column)
+ {
+ return array_key_exists($column, $this->_data) ? $this->_data[$column] : null;
+ }
+
+ /**
+ * Returns the name of the table managed by this GenericEntity.
+ *
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Return the data.
+ *
+ * @return array
+ */
+ public function getData(): array
+ {
+ return $this->_data;
+ }
+
+ /**
+ * Return the column name of the primary key.
+ *
+ * @return string
+ */
+ public function getPk(): string
+ {
+ return $this->_pk;
+ }
+}
\ No newline at end of file
diff --git a/src/LightQL/Entities/IValueTransformer.php b/src/LightQL/Entities/IValueTransformer.php
new file mode 100644
index 0000000..595edf8
--- /dev/null
+++ b/src/LightQL/Entities/IValueTransformer.php
@@ -0,0 +1,67 @@
+
+ * @copyright 2018 Aliens Group, Inc.
+ * @license MIT
+ * @version 1.0.0
+ * @link http://lightql.na2axl.tk
+ */
+
+namespace ElementaryFramework\LightQL\Entities;
+
+/**
+ * IValueTransformer
+ *
+ * Defines a class as the value transformer of an entity.
+ *
+ * @category Entities
+ * @package LightQL
+ * @author Nana Axel
+ * @link http://lightql.na2axl.tk/docs/api/LightQL/Entities/IValueTransformer
+ */
+interface IValueTransformer
+{
+ /**
+ * Transforms a value before send it to the database.
+ *
+ * @param Entity $entity The entity which will be sent to the database.
+ * @param string $property The name of the property to transform value.
+ *
+ * @return mixed The transformed value.
+ */
+ function toDatabaseValue(Entity $entity, string $property);
+
+ /**
+ * Transforms a value before send it to the entity.
+ *
+ * @param string $table The name of the table which provide the value.
+ * @param string $column The name of the column which hold the value.
+ * @param mixed $value The value to transform.
+ *
+ * @return mixed The transformed value.
+ */
+ function toEntityValue(string $table, string $column, $value);
+}
diff --git a/src/LightQL/Entities/IValueValidator.php b/src/LightQL/Entities/IValueValidator.php
new file mode 100644
index 0000000..079413e
--- /dev/null
+++ b/src/LightQL/Entities/IValueValidator.php
@@ -0,0 +1,56 @@
+
+ * @copyright 2018 Aliens Group, Inc.
+ * @license MIT
+ * @version 1.0.0
+ * @link http://lightql.na2axl.tk
+ */
+
+namespace ElementaryFramework\LightQL\Entities;
+
+/**
+ * IValueValidator
+ *
+ * Defines a class as the value validator of an entity.
+ *
+ * @category Entities
+ * @package LightQL
+ * @author Nana Axel
+ * @link http://lightql.na2axl.tk/docs/api/LightQL/Entities/IValueValidator
+ */
+interface IValueValidator
+{
+ /**
+ * Validates a value before send it to the database.
+ *
+ * @param Entity $entity The entity which will be sent to the database.
+ * @param string $property The name of the property to validate.
+ *
+ * @return bool true if the value passes the validator, false otherwise.
+ */
+ function validate(Entity $entity, string $property): bool;
+}
diff --git a/src/LightQL/Entities/Query.php b/src/LightQL/Entities/Query.php
index e718042..19e7789 100644
--- a/src/LightQL/Entities/Query.php
+++ b/src/LightQL/Entities/Query.php
@@ -1,174 +1,166 @@
-
- * @copyright 2018 Aliens Group, Inc.
- * @license MIT
- * @version 1.0.0
- * @link http://lightql.na2axl.tk
- */
-
-namespace ElementaryFramework\LightQL\Entities;
-
-use ElementaryFramework\LightQL\Exceptions\QueryException;
-
-/**
- * Query
- *
- * Manage, run and get results from named queries.
- *
- * @category Entities
- * @package LightQL
- * @author Nana Axel
- * @link http://lightql.na2axl.tk/docs/api/LightQL/Entities/Query
- */
-class Query
-{
- /**
- * The entity manager running this query.
- *
- * @var EntityManager
- */
- private $_entityManager;
-
- /**
- * The reflection class of the managed entity.
- *
- * @var \ReflectionClass
- */
- private $_entityReflection;
-
- /**
- * The named query string.
- *
- * @var string
- */
- private $_namedQuery;
-
- /**
- * Query parameters.
- *
- * @var array
- */
- private $_parameters = array();
-
- /**
- * The query executed by this instance.
- *
- * @var \PDOStatement
- */
- private $_query = null;
-
- /**
- * Query constructor.
- *
- * @param EntityManager $manager
- */
- public function __construct(EntityManager $manager)
- {
- $this->_entityManager = $manager;
- }
-
- /**
- * Sets the reflection class of the managed entity.
- *
- * @param \ReflectionClass $entity The managed entity reflection class instance.
- */
- public function setEntity(\ReflectionClass $entity)
- {
- $this->_entityReflection = $entity;
- }
-
- /**
- * Sets the named query to execute.
- *
- * @param string $query The named query.
- */
- public function setQuery(string $query)
- {
- $this->_namedQuery = $query;
- }
-
- /**
- * Defines the value of one of query parameters.
- *
- * @param string $name The name of the parameter in the query.
- * @param mixed $value The value of this parameter.
- */
- public function setParam(string $name, $value)
- {
- $this->_parameters[$name] = $value;
- }
-
- /**
- * Executes the query.
- *
- * @return bool
- */
- public function run(): bool
- {
- try {
- $this->_query = $this->_entityManager->getLightQL()->prepare($this->_namedQuery);
-
- foreach ($this->_parameters as $name => $value) {
- $this->_query->bindValue($name, $value);
- }
-
- return $this->_query->execute();
- } catch (\Exception $e) {
- throw new QueryException($e->getMessage());
- }
- }
-
- /**
- * Returns the set of results after the execution of the query.
- *
- * @return Entity[]
- */
- public function getResults(): array
- {
- if ($this->_query === null) {
- throw new QueryException("Cannot get results, have you ran the query?");
- }
-
- $results = array_map(function ($item) {
- return $this->_entityReflection->newInstance($item);
- }, $this->_query->fetchAll());
-
- return $results;
- }
-
- /**
- * Returns the first result of the set after the execution
- * of the query.
- *
- * @return IEntity|null
- */
- public function getFirstResult(): ?IEntity
- {
- $results = $this->getResults();
- return count($results) > 0 ? $results[0] : null;
- }
-}
+
+ * @copyright 2018 Aliens Group, Inc.
+ * @license MIT
+ * @version 1.0.0
+ * @link http://lightql.na2axl.tk
+ */
+
+namespace ElementaryFramework\LightQL\Entities;
+
+use ElementaryFramework\Annotations\Annotations;
+use ElementaryFramework\LightQL\Exceptions\QueryException;
+use ElementaryFramework\LightQL\Sessions\Facade;
+
+/**
+ * Query
+ *
+ * Manage, run and get results from named queries.
+ *
+ * @category Entities
+ * @package LightQL
+ * @author Nana Axel
+ * @link http://lightql.na2axl.tk/docs/api/LightQL/Entities/Query
+ */
+class Query
+{
+ /**
+ * The facade running this query.
+ *
+ * @var Facade
+ */
+ private $_facade;
+
+ /**
+ * The named query string.
+ *
+ * @var string
+ */
+ private $_namedQuery;
+
+ /**
+ * Query parameters.
+ *
+ * @var array
+ */
+ private $_parameters = array();
+
+ /**
+ * The query executed by this instance.
+ *
+ * @var \PDOStatement
+ */
+ private $_query = null;
+
+ /**
+ * Query constructor.
+ *
+ * @param Facade $facade
+ */
+ public function __construct(Facade $facade)
+ {
+ $this->_facade = $facade;
+ }
+
+ /**
+ * Sets the named query to execute.
+ *
+ * @param string $query The named query.
+ */
+ public function setQuery(string $query)
+ {
+ $this->_namedQuery = $query;
+ }
+
+ /**
+ * Defines the value of one of query parameters.
+ *
+ * @param string $name The name of the parameter in the query.
+ * @param mixed $value The value of this parameter.
+ */
+ public function setParam(string $name, $value)
+ {
+ $this->_parameters[$name] = $value;
+ }
+
+ /**
+ * Executes the query.
+ *
+ * @return bool
+ */
+ public function run(): bool
+ {
+ try {
+ $this->_query = $this->_facade
+ ->getEntityManager()
+ ->getLightQL()
+ ->prepare($this->_namedQuery);
+
+ foreach ($this->_parameters as $name => $value) {
+ $this->_query->bindValue($name, $value);
+ }
+
+ return $this->_query->execute();
+ } catch (\Exception $e) {
+ throw new QueryException($e->getMessage());
+ }
+ }
+
+ /**
+ * Returns the set of results after the execution of the query.
+ *
+ * @return EntitiesCollection
+ */
+ public function getResults(): EntitiesCollection
+ {
+ if ($this->_query === null) {
+ throw new QueryException("Cannot get results, have you ran the query?");
+ }
+
+ $className = $this->_facade->getEntityClassName();
+
+ $results = $this->_facade->_parseRawEntities(
+ $this->_query->fetchAll(),
+ $className,
+ Annotations::ofClass($className, "@entity")
+ );
+
+ return new EntitiesCollection($results);
+ }
+
+ /**
+ * Returns the first result of the set after the execution
+ * of the query.
+ *
+ * @return IEntity|null
+ */
+ public function getFirstResult(): ?IEntity
+ {
+ $results = $this->getResults();
+ return count($results) > 0 ? $results[0] : null;
+ }
+}
diff --git a/src/LightQL/Exceptions/EntityException.php b/src/LightQL/Exceptions/EntityException.php
index 6e6863c..0f9ad94 100644
--- a/src/LightQL/Exceptions/EntityException.php
+++ b/src/LightQL/Exceptions/EntityException.php
@@ -32,6 +32,8 @@
namespace ElementaryFramework\LightQL\Exceptions;
+use Throwable;
+
/**
* Entity Exception
*
@@ -42,4 +44,8 @@
*/
class EntityException extends \Exception
{
+ public function __construct($message = "", $code = 0, Throwable $previous = null)
+ {
+ parent::__construct($message, $code, $previous);
+ }
}
diff --git a/src/LightQL/Exceptions/OperationCancelledException.php b/src/LightQL/Exceptions/OperationCancelledException.php
new file mode 100644
index 0000000..c47d3a3
--- /dev/null
+++ b/src/LightQL/Exceptions/OperationCancelledException.php
@@ -0,0 +1,49 @@
+
+ * @copyright 2018 Aliens Group, Inc.
+ * @license MIT
+ * @version 1.0.0
+ * @link http://lightql.na2axl.tk
+ */
+
+namespace ElementaryFramework\LightQL\Exceptions;
+
+/**
+ * Operation Cancelled Exception
+ *
+ * @category Exceptions
+ * @package LightQL
+ * @author Nana Axel
+ * @link http://lightql.na2axl.tk/docs/api/LightQL/Exceptions/OperationCancelledException
+ */
+class OperationCancelledException extends \Exception
+{
+ public function __construct(string $listenerClassName)
+ {
+ parent::__construct("The operation has been cancelled by the listener: \"{$listenerClassName}\"");
+ }
+}
diff --git a/src/LightQL/Exceptions/ValueValidatorException.php b/src/LightQL/Exceptions/ValueValidatorException.php
new file mode 100644
index 0000000..146d586
--- /dev/null
+++ b/src/LightQL/Exceptions/ValueValidatorException.php
@@ -0,0 +1,54 @@
+
+ * @copyright 2018 Aliens Group, Inc.
+ * @license MIT
+ * @version 1.0.0
+ * @link http://lightql.na2axl.tk
+ */
+
+namespace ElementaryFramework\LightQL\Exceptions;
+
+/**
+ * Entity Exception
+ *
+ * @category Exceptions
+ * @package LightQL
+ * @author Nana Axel
+ * @link http://lightql.na2axl.tk/docs/api/LightQL/Exceptions/ValueValidatorException
+ */
+class ValueValidatorException extends \Exception
+{
+ /**
+ * Thrown when a value can't pass the validator of an entity.
+ *
+ * @param string $property The name of the property.
+ */
+ public function __construct(string $property)
+ {
+ parent::__construct("The value of the property \"{$property}\" have not pass the validator.");
+ }
+}
diff --git a/src/LightQL/LightQL.php b/src/LightQL/LightQL.php
index 5f8b9fd..b192ef3 100644
--- a/src/LightQL/LightQL.php
+++ b/src/LightQL/LightQL.php
@@ -38,6 +38,7 @@
use ElementaryFramework\LightQL\Annotations\EntityAnnotation;
use ElementaryFramework\LightQL\Annotations\IdAnnotation;
use ElementaryFramework\LightQL\Annotations\IdGeneratorAnnotation;
+use ElementaryFramework\LightQL\Annotations\ListenerAnnotation;
use ElementaryFramework\LightQL\Annotations\ManyToManyAnnotation;
use ElementaryFramework\LightQL\Annotations\ManyToOneAnnotation;
use ElementaryFramework\LightQL\Annotations\NamedQueryAnnotation;
@@ -45,8 +46,11 @@
use ElementaryFramework\LightQL\Annotations\OneToManyAnnotation;
use ElementaryFramework\LightQL\Annotations\OneToOneAnnotation;
use ElementaryFramework\LightQL\Annotations\PersistenceUnitAnnotation;
+use ElementaryFramework\LightQL\Annotations\PkClassAnnotation;
use ElementaryFramework\LightQL\Annotations\SizeAnnotation;
+use ElementaryFramework\LightQL\Annotations\TransformerAnnotation;
use ElementaryFramework\LightQL\Annotations\UniqueAnnotation;
+use ElementaryFramework\LightQL\Annotations\ValidatorAnnotation;
use ElementaryFramework\LightQL\Exceptions\LightQLException;
/**
@@ -78,6 +82,7 @@ public static function registerAnnotations()
$manager->registerAnnotation("entity", EntityAnnotation::class);
$manager->registerAnnotation("id", IdAnnotation::class);
$manager->registerAnnotation("idGenerator", IdGeneratorAnnotation::class);
+ $manager->registerAnnotation("listener", ListenerAnnotation::class);
$manager->registerAnnotation("manyToMany", ManyToManyAnnotation::class);
$manager->registerAnnotation("manyToOne", ManyToOneAnnotation::class);
$manager->registerAnnotation("namedQuery", NamedQueryAnnotation::class);
@@ -85,8 +90,11 @@ public static function registerAnnotations()
$manager->registerAnnotation("oneToMany", OneToManyAnnotation::class);
$manager->registerAnnotation("oneToOne", OneToOneAnnotation::class);
$manager->registerAnnotation("persistenceUnit", PersistenceUnitAnnotation::class);
+ $manager->registerAnnotation("pkClass", PkClassAnnotation::class);
$manager->registerAnnotation("size", SizeAnnotation::class);
+ $manager->registerAnnotation("transformer", TransformerAnnotation::class);
$manager->registerAnnotation("unique", UniqueAnnotation::class);
+ $manager->registerAnnotation("validator", ValidatorAnnotation::class);
}
/**
@@ -255,7 +263,7 @@ public function __construct(array $options = null)
return false;
}
} else {
- if (isset($options["port"]) && is_int($options["port"] * 1)) {
+ if (isset($options["port"]) && is_int($options["port"] * 1) && intval($options["port"]) > -1) {
$port = $options["port"];
}
@@ -352,7 +360,7 @@ public function __construct(array $options = null)
$stack[] = is_int($key) ? $value : "{$key}={$value}";
}
- $this->_dsn = $this->_driver . ":" . implode($stack, ";");
+ $this->_dsn = $this->_driver . ":" . implode(";", $stack);
if (in_array($this->_dbms, ['mariadb', 'mysql', 'pgsql', 'sybase', 'mssql']) && isset($options['charset'])) {
$commands[] = "SET NAMES '{$options['charset']}'";
@@ -445,7 +453,7 @@ public function where($condition): LightQL
if (is_int($column)) {
$this->_where .= $value;
} else {
- $parts = explode(" ", $this->parseValue($value));
+ $parts = explode(" ", $value);
foreach (self::$_operators as $operator) {
if (in_array($operator, $parts, true) && $parts[0] === $operator) {
$operand = $operator;
@@ -488,7 +496,7 @@ public function order(string $column, string $mode = "ASC"): LightQL
*/
public function limit(int $offset, int $count): LightQL
{
- $this->_limit = " LIMIT {$offset}, {$count} ";
+ $this->_limit = " LIMIT {$count} OFFSET {$offset}";
return $this;
}
@@ -614,7 +622,7 @@ protected function resetClauses()
*
* @throws \ElementaryFramework\LightQL\Exceptions\LightQLException
*
- * @return array
+ * @return array|null
*/
public function selectFirst($columns = "*")
{
@@ -799,13 +807,13 @@ public function count($columns = "*")
$getFieldsData = $this->prepare($this->_queryString);
if ($getFieldsData->execute() !== false) {
+ $this->resetClauses();
+
if (null === $this->_group) {
- $this->resetClauses();
$data = $getFieldsData->fetch();
return (int) $data['lightql_count'];
}
- $this->resetClauses();
$res = array();
while ($data = $getFieldsData->fetch()) {
@@ -834,13 +842,13 @@ public function insert(array $fieldsAndValues): bool
foreach ($fieldsAndValues as $column => $value) {
$columns[] = $column;
- $values[] = $this->parseValue($value);
+ $values[] = $value;
}
$column = implode(",", $columns);
$value = implode(",", $values);
- $this->_queryString = trim("INSERT INTO {$this->table}({$column}) VALUE ({$value})");
+ $this->_queryString = trim("INSERT INTO {$this->table}({$column}) VALUES ({$value})");
$getFieldsData = $this->prepare($this->_queryString);
@@ -901,7 +909,7 @@ public function update(array $fieldsAndValues): bool
if (is_array($fieldsAndValues)) {
foreach ($fieldsAndValues as $column => $value) {
$count--;
- $updates .= "{$column} = " . $this->parseValue($value);
+ $updates .= "{$column} = " . $value;
$updates .= ($count != 0) ? ", " : "";
}
} else {
@@ -1004,6 +1012,18 @@ public function quote($value): string
return $this->_pdo->quote($value);
}
+ /**
+ * Checks if we are currently inside a transaction.
+ *
+ * @uses \PDO::inTransaction()
+ *
+ * @return bool
+ */
+ public function inTransaction(): bool
+ {
+ return $this->_pdo->inTransaction();
+ }
+
/**
* Disable auto commit mode and start a transaction.
*
@@ -1054,7 +1074,7 @@ public function parseValue($value): string
} elseif (is_bool($value)) {
return $value ? "1" : "0";
} else {
- return strval($value);
+ return $this->quote(strval($value));
}
}
}
diff --git a/src/LightQL/Persistence/PersistenceUnit.php b/src/LightQL/Persistence/PersistenceUnit.php
index 383063e..c1a182e 100644
--- a/src/LightQL/Persistence/PersistenceUnit.php
+++ b/src/LightQL/Persistence/PersistenceUnit.php
@@ -46,6 +46,13 @@
*/
class PersistenceUnit
{
+ /**
+ * The key used to create this persistence unit.
+ *
+ * @var string
+ */
+ private $_key;
+
/**
* The DBMS.
*
@@ -81,6 +88,13 @@ class PersistenceUnit
*/
private $_password;
+ /**
+ * The port number of the DBMS.
+ *
+ * @var int
+ */
+ private $_port = -1;
+
/**
* The list of registered persistence unit files.
*
@@ -123,15 +137,46 @@ public static function purge()
private function __construct(string $key)
{
if (array_key_exists($key, self::$_registry)) {
- $filename = basename(self::$_registry[$key]);
+ $this->_key = $key;
+
+ $filepath = self::$_registry[$key];
+
+ if (!file_exists($filepath)) {
+ throw new PersistenceUnitException("The persistence unit file at the path \"{$filepath}\" cannot be found.");
+ }
+
+ $filename = basename($filepath);
$parts = explode(".", $filename);
$extension = $parts[count($parts) - 1];
$content = null;
if ($extension === "ini") {
- $content = parse_ini_file(self::$_registry[$key]);
+ $content = parse_ini_file($filepath);
} elseif ($extension === "json") {
- $content = json_decode(file_get_contents(self::$_registry[$key]), true);
+ $content = json_decode(file_get_contents($filepath), true);
+ } elseif ($extension === "xml") {
+ $dom = new \DOMDocument("1.0", "utf-8");
+ $dom->loadXML(file_get_contents($filepath));
+ if ($dom->documentElement->nodeName !== "persistenceUnit") {
+ throw new PersistenceUnitException("Invalid persistence unit XML configuration file provided.");
+ } else {
+ /** @var \DOMElement $node */
+ foreach ($dom->documentElement->childNodes as $node) {
+ switch (strtolower($node->nodeName)) {
+ case "#text":
+ break;
+ case "dbms": $content["DBMS"] = $node->nodeValue; break;
+ case "hostname": $content["Hostname"] = $node->nodeValue; break;
+ case "database": $content["DatabaseName"] = $node->nodeValue; break;
+ case "username": $content["Username"] = $node->nodeValue; break;
+ case "password": $content["Password"] = $node->nodeValue; break;
+ case "port":
+ $content["Port"] = intval($node->nodeValue);
+ break;
+ default: throw new PersistenceUnitException("Invalid persistence unit XML configuration file provided. Unknown configuration item \"{$node->nodeName}\"");
+ }
+ }
+ }
} else {
throw new PersistenceUnitException("Unsupported file type used to create persistence unit {$filename}.");
}
@@ -165,8 +210,12 @@ private function __construct(string $key)
} else {
throw new PersistenceUnitException("Malformed persistence unit configuration file, missing the Password value.");
}
+
+ if (array_key_exists("Port", $content)) {
+ $this->_port = intval($content["Port"]);
+ }
} else {
- throw new PersistenceUnitException('Unable to find the persistence unit with the key "' . $key . '". Have you registered this persistence unit?');
+ throw new PersistenceUnitException("Unable to find the persistence unit with the key \"{$key}\". Have you registered this persistence unit?");
}
}
@@ -176,8 +225,9 @@ private function __construct(string $key)
* @param string $key The persistence unit name.
*
* @return PersistenceUnit
+ * @throws PersistenceUnitException
*/
- public static function create(string $key)
+ public static function create(string $key): PersistenceUnit
{
if (array_key_exists($key, self::$_units)) {
return self::$_units[$key];
@@ -186,12 +236,22 @@ public static function create(string $key)
}
}
+ /**
+ * Returns the persistence unit key.
+ *
+ * @return string
+ */
+ public function getKey(): string
+ {
+ return $this->_key;
+ }
+
/**
* Returns the DBMS.
*
* @return string
*/
- public function getDbms()
+ public function getDbms(): string
{
return $this->_dbms;
}
@@ -235,4 +295,14 @@ public function getUsername(): string
{
return $this->_username;
}
+
+ /**
+ * Returns the port number.
+ *
+ * @return int
+ */
+ public function getPort(): int
+ {
+ return $this->_port;
+ }
}
diff --git a/src/LightQL/Sessions/Facade.php b/src/LightQL/Sessions/Facade.php
index d607a68..3907bf0 100644
--- a/src/LightQL/Sessions/Facade.php
+++ b/src/LightQL/Sessions/Facade.php
@@ -1,576 +1,684 @@
-
- * @copyright 2018 Aliens Group, Inc.
- * @license MIT
- * @version 1.0.0
- * @link http://lightql.na2axl.tk
- */
-
-namespace ElementaryFramework\LightQL\Sessions;
-
-use ElementaryFramework\Annotations\Annotations;
-use ElementaryFramework\Annotations\Exceptions\AnnotationException;
-
-use ElementaryFramework\LightQL\Annotations\NamedQueryAnnotation;
-use ElementaryFramework\LightQL\Entities\Entity;
-use ElementaryFramework\LightQL\Entities\EntityManager;
-use ElementaryFramework\LightQL\Entities\IEntity;
-use ElementaryFramework\LightQL\Entities\Query;
-use ElementaryFramework\LightQL\Exceptions\EntityException;
-use ElementaryFramework\LightQL\Exceptions\FacadeException;
-use ElementaryFramework\LightQL\Persistence\PersistenceUnit;
-
-/**
- * Facade
- *
- * Base class for all entity facades.
- *
- * @abstract
- * @category Sessions
- * @package LightQL
- * @author Nana Axel
- * @link http://lightql.na2axl.tk/docs/api/LightQL/Sessions/Facade
- */
-abstract class Facade implements IFacade
-{
- /**
- * The entity manager of this facade.
- *
- * @var EntityManager
- */
- protected $entityManager;
-
- /**
- * The entity class name managed by this facade.
- *
- * @var \ReflectionClass
- */
- private $_class;
-
- /**
- * Facade constructor.
- *
- * @param string $class The entity class name managed by this facade.
- *
- * @throws EntityException When the entity class or object doesn't have an @entity annotation.
- * @throws FacadeException When the "entityManager" property of this Facade doesn't have a @persistenceUnit annotation.
- * @throws FacadeException When the entity class or object doesn't inherit from the Entity class.
- * @throws AnnotationException When the Facade is unable to read an annotation.
- */
- public function __construct($class)
- {
- if (!Annotations::propertyHasAnnotation($this, "entityManager", "@persistenceUnit")) {
- throw new FacadeException("Cannot create the entity facade. The property \"entityManager\" has no @persistenceUnit annotation.");
- }
-
- if (!Annotations::classHasAnnotation($class, "@entity")) {
- throw new EntityException("Cannot create an entity without the @entity annotation.");
- }
-
- if (!is_subclass_of($class, Entity::class)) {
- throw new FacadeException("Unable to create a facade. The entity class or object seems to be invalid.");
- }
-
- $this->_class = new \ReflectionClass($class);
-
- $annotations = Annotations::ofProperty($this, "entityManager", "@persistenceUnit");
- $this->entityManager = new EntityManager(PersistenceUnit::create($annotations[0]->name));
- }
-
- /**
- * Creates an entity.
- *
- * @param Entity $entity The entity to create.
- *
- * @throws FacadeException
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\EntityException
- */
- public function create(Entity &$entity)
- {
- if (!$this->_class->isInstance($entity)) {
- throw new FacadeException("Cannot create entity. The type of the entity is not valid for this facade.");
- }
-
- try {
- $this->entityManager->persist($entity);
-
- $columns = $entity->getColumns();
- foreach ($columns as $property => $column) {
- if ($column->isOneToMany) {
- $this->_fetchOneToMany($entity, $property);
- } elseif ($column->isManyToOne) {
- $this->_fetchManyToOne($entity, $property);
- } elseif ($column->isManyToMany) {
- $this->_fetchManyToMany($entity, $property);
- } elseif ($column->isOneToOne) {
- $this->_fetchOneToOne($entity, $property);
- }
- }
- } catch (\Exception $e) {
- throw new FacadeException($e->getMessage());
- }
- }
-
- /**
- * Edit an entity.
- *
- * @param Entity $entity The entity to edit.
- *
- * @throws FacadeException
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\EntityException
- */
- public function edit(Entity &$entity)
- {
- if (!$this->_class->isInstance($entity)) {
- throw new FacadeException("Cannot edit entity. The type of the entity is not valid for this facade.");
- }
-
- $this->entityManager->merge($entity);
- }
-
- /**
- * Delete an entity.
- *
- * @param Entity $entity The entity to delete.
- *
- * @throws FacadeException
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\EntityException
- */
- public function delete(Entity &$entity)
- {
- if (!$this->_class->isInstance($entity)) {
- throw new FacadeException("Cannot edit entity. The type of the entity is not valid for this facade.");
- }
-
- $this->entityManager->merge($entity);
- $this->entityManager->delete($entity);
- }
-
- /**
- * Find an entity.
- *
- * @param mixed $id The id of the entity to find
- *
- * @return Entity
- *
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\LightQLException
- */
- public function find($id): Entity
- {
- $annotations = Annotations::ofClass($this->getEntityClassName(), "@entity");
-
- return $this->_parseRawEntity(
- $this->entityManager->find($this->getEntityClassName(), $id),
- $annotations
- );
- }
-
- /**
- * Find all entities.
- *
- * @return Entity[]
- *
- * @throws EntityException
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\LightQLException
- */
- public function findAll(): array
- {
- $annotations = Annotations::ofClass($this->getEntityClassName(), "@entity");
-
- $rawEntities = $this->entityManager
- ->getLightQL()
- ->from($annotations[0]->table)
- ->selectArray();
-
- return $this->_parseRawEntities($rawEntities, $annotations);
- }
-
- /**
- * Find all entities in the given range.
- *
- * @param int $start The starting offset.
- * @param int $length The number of entities to find.
- *
- * @return Entity[]
- *
- * @throws EntityException
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\LightQLException
- */
- public function findRange(int $start, int $length): array
- {
- $annotations = Annotations::ofClass($this->getEntityClassName(), "@entity");
-
- $rawEntities = $this->entityManager
- ->getLightQL()
- ->from($annotations[0]->table)
- ->limit($start, $length)
- ->selectArray();
-
- return $this->_parseRawEntities($rawEntities, $annotations);
- }
-
- /**
- * Count the number of entities.
- *
- * @return int
- *
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\LightQLException
- */
- public function count(): int
- {
- $annotations = Annotations::ofClass($this->getEntityClassName(), "@entity");
-
- return $this->entityManager
- ->getLightQL()
- ->from($annotations[0]->table)
- ->count();
- }
-
- /**
- * Returns the entity class name of this facade.
- *
- * @return string
- */
- public function getEntityClassName(): string
- {
- return $this->_class->getName();
- }
-
- /**
- * Returns the entity manager for this facade.
- *
- * @return EntityManager
- */
- public function getEntityManager(): EntityManager
- {
- return $this->entityManager;
- }
-
- /**
- * Get a named query.
- *
- * @param string $name The name of the query.
- *
- * @return Query
- *
- * @throws FacadeException
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- */
- public function getNamedQuery(string $name): Query
- {
- if (!Annotations::classHasAnnotation($this->_class->name, "@namedQuery")) {
- throw new FacadeException("The {$this->_class->name} has no @namedQuery annotation.");
- }
-
- $namedQueries = Annotations::ofClass($this->getEntityClassName(), "@namedQuery");
- $query = null;
-
- /** @var NamedQueryAnnotation $namedQuery */
- foreach ($namedQueries as $namedQuery) {
- if ($namedQuery->name === $name) {
- $query = $namedQuery->query;
- break;
- }
- }
-
- if ($query === null) {
- throw new FacadeException("The {$this->_class->name} has no @namedQuery annotation with the name {$name}.");
- }
-
- $q = new Query($this->entityManager);
- $q->setEntity($this->_class);
- $q->setQuery($query);
-
- return $q;
- }
-
- /**
- * Fetch data for a many-to-many relation.
- *
- * @param IEntity $entity The managed entity.
- * @param string $property The property in many-to-many relation.
- *
- * @throws EntityException
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\LightQLException
- */
- private function _fetchManyToMany(&$entity, $property)
- {
- $manyToMany = Annotations::ofProperty($entity, $property, "@manyToMany");
- $column = Annotations::ofProperty($entity, $property, "@column");
- $entityAnnotations = Annotations::ofClass($entity, "@entity");
-
- $mappedPropertyName = null;
-
- $referencedEntity = new $manyToMany[0]->entity;
- foreach ($referencedEntity->getColumns() as $p => $c) {
- if ($c->isManyToMany) {
- $mappedManyToMany = Annotations::ofProperty($referencedEntity, $p, "@manyToMany");
- if ($mappedManyToMany[0]->crossTable === $manyToMany[0]->crossTable) {
- $mappedPropertyName = $p;
- break;
- }
- }
- }
- unset($referencedEntity);
-
- if ($mappedPropertyName === null) {
- throw new EntityException("Unable to find a suitable property with a @manyToMany annotation in the entity \"$manyToMany[0]->entity\".");
- }
-
- $mappedPropertyManyToManyAnnotation = Annotations::ofProperty($manyToMany[0]->entity, $mappedPropertyName, "@manyToMany");
- $mappedPropertyColumnAnnotation = Annotations::ofProperty($manyToMany[0]->entity, $mappedPropertyName, "@column");
- $referencedEntityAnnotations = Annotations::ofClass($manyToMany[0]->entity, "@entity");
-
- $lightql = $this->entityManager->getLightQL();
-
- $results = $lightql
- ->from($manyToMany[0]->crossTable)
- ->where(array("{$manyToMany[0]->crossTable}.{$manyToMany[0]->referencedColumn}" => $lightql->quote($entity->get($column[0]->name))))
- ->joinArray(
- "{$referencedEntityAnnotations[0]->table}.*",
- array(
- array(
- "side" => "LEFT",
- "table" => $referencedEntityAnnotations[0]->table,
- "cond" => "{$manyToMany[0]->crossTable}.{$mappedPropertyAnnotation[0]->referencedColumn} = {$referencedEntityAnnotations[0]->table}.{$mappedPropertyColumnAnnotation[0]->name}"
- )
- )
- );
-
- $className = $manyToMany[0]->entity;
- $entity->{$property} = array_map(function($item) use ($manyToMany, $className) {
- return new $className($item);
- }, $results);
- }
-
- /**
- * Fetch data for a one-to-many relation.
- *
- * @param IEntity $entity The managed entity.
- * @param string $property The property in one-to-many relation.
- *
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\LightQLException
- */
- private function _fetchOneToMany(&$entity, $property)
- {
- $oneToMany = Annotations::ofProperty($entity, $property, "@oneToMany");
- $column = Annotations::ofProperty($entity, $property, "@column");
- $referencedEntityAnnotations = Annotations::ofClass($oneToMany[0]->entity, "@entity");
-
- $mappedPropertyName = $this->_resolveMappedPropertyName($oneToMany[0]->entity, "ManyToOne", $oneToMany[0]->referencedColumn);
-
- if ($mappedPropertyName === null) {
- throw new EntityException("Unable to find a suitable property with @manyToOne annotation in the entity \"{$oneToMany[0]->entity}\".");
- }
-
- $mappedPropertyManyToOneAnnotation = Annotations::ofProperty($oneToMany[0]->entity, $mappedPropertyName, "@manyToOne");
-
- $lightql = $this->entityManager->getLightQL();
-
- $result = $lightql
- ->from($referencedEntityAnnotations[0]->table)
- ->where(array("{$referencedEntityAnnotations[0]->table}.{$oneToMany[0]->referencedColumn}" => $lightql->quote($entity->get($column[0]->name))))
- ->selectFirst("{$referencedEntityAnnotations[0]->table}.*");
-
- $className = $oneToMany[0]->entity;
-
- $entity->{$property} = $result;
-
- if ($result !== null) {
- $entity->{$property} = new $className($result);
- }
- }
-
- /**
- * Fetch data for a many-to-one relation.
- *
- * @param IEntity $entity The managed entity.
- * @param string $property The property in many-to-one relation.
- *
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\LightQLException
- */
- private function _fetchManyToOne(&$entity, $property)
- {
- $manyToOne = Annotations::ofProperty($entity, $property, "@manyToOne");
- $column = Annotations::ofProperty($entity, $property, "@column");
- $referencedEntityAnnotations = Annotations::ofClass($manyToOne[0]->entity, "@entity");
-
- $mappedPropertyName = $this->_resolveMappedPropertyName($manyToOne[0]->entity, "OneToMany", $manyToOne[0]->referencedColumn);
-
- if ($mappedPropertyName === null) {
- throw new EntityException("Unable to find a suitable property with @oneToMany annotation in the entity \"{$manyToOne[0]->entity}\".");
- }
-
- $lightql = $this->entityManager->getLightQL();
-
- $results = $lightql
- ->from($referencedEntityAnnotations[0]->table)
- ->where(array("{$referencedEntityAnnotations[0]->table}.{$manyToOne[0]->referencedColumn}" => $lightql->quote($entity->get($column[0]->name))))
- ->selectArray("{$referencedEntityAnnotations[0]->table}.*");
-
- $entity->{$property} = array_map(function($item) use ($manyToOne, $entity, $mappedPropertyName) {
- $className = $manyToOne[0]->entity;
- $e = new $className($item);
- $e->{$mappedPropertyName} = &$entity;
- return $e;
- }, $results);
- }
-
- /**
- * Fetch data for a one-to-one relation.
- *
- * @param IEntity $entity The managed entity.
- * @param string $property The property in one-to-one relation.
- *
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\LightQLException
- */
- private function _fetchOneToOne(&$entity, $property)
- {
- $oneToOne = Annotations::ofProperty($entity, $property, "@oneToOne");
- $column = Annotations::ofProperty($entity, $property, "@column");
- $referencedEntityAnnotations = Annotations::ofClass($oneToOne[0]->entity, "@entity");
-
- $mappedPropertyName = $this->_resolveMappedPropertyName($oneToOne[0]->entity, "OneToOne", $oneToOne[0]->referencedColumn);
-
- if ($mappedPropertyName === null) {
- throw new EntityException("Unable to find a suitable property with @oneToOne annotation in the entity \"{$oneToOne[0]->entity}\".");
- }
-
- $mappedPropertyAnnotation = Annotations::ofProperty($oneToOne[0]->entity, $mappedPropertyName, "@oneToOne");
-
- $lightql = $this->entityManager->getLightQL();
-
- $result = $lightql
- ->from($referencedEntityAnnotations[0]->table)
- ->where(array("{$referencedEntityAnnotations[0]->table}.{$oneToOne[0]->referencedColumn}" => $lightql->quote($entity->get($column[0]->name))))
- ->selectFirst("{$referencedEntityAnnotations[0]->table}.*");
-
- $className = $oneToOne[0]->entity;
-
- $entity->{$property} = $result;
-
- if ($result !== null) {
- $entity->{$property} = new $className($result);
- $entity->{$property}->{$mappedPropertyName} = &$entity;
- }
- }
-
- /**
- * Resolve the name of a property mapped by an annotation.
- *
- * @param string $entityClass The class name of the mapped property.
- * @param string $check The type of annotation to find.
- * @param string $column The mapped column name.
- *
- * @return string|null
- */
- private function _resolveMappedPropertyName(string $entityClass, string $check, string $column): string
- {
- $mappedPropertyName = null;
-
- $referencedEntity = new $entityClass;
- foreach ($referencedEntity->getColumns() as $p => $c) {
- if ($c->{"is{$check}"} && $c->getName() === $column) {
- $mappedPropertyName = $p;
- break;
- }
- }
- unset($referencedEntity);
-
- return $mappedPropertyName;
- }
-
- /**
- * Parse a set of raw data to a set of Entities.
- *
- * @param array $rawEntities The set of raw entities data provided fromm database.
- * @param array $annotations The set of entity annotations.
- *
- * @return Entity[]
- *
- * @throws EntityException
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\LightQLException
- */
- private function _parseRawEntities($rawEntities, $annotations): array
- {
- $entities = array();
-
- foreach ($rawEntities as $rawEntity) {
- array_push($entities, $this->_parseRawEntity($rawEntity, $annotations));
- }
-
- return $entities;
- }
-
- /**
- * Parses raw data to Entity.
- *
- * @param array $rawEntity Raw entity data provided from database.
- * @param array $annotations The set of entity annotations.
- *
- * @return Entity
- *
- * @throws EntityException
- * @throws \ElementaryFramework\Annotations\Exceptions\AnnotationException
- * @throws \ElementaryFramework\LightQL\Exceptions\LightQLException
- */
- private function _parseRawEntity($rawEntity, $annotations): Entity
- {
- /** @var Entity $entity */
- $entity = $this->_class->newInstance($rawEntity);
-
- if ($annotations[0]->fetchMode === Entity::FETCH_EAGER) {
- $properties = $this->_class->getProperties();
-
- foreach ($properties as $property) {
- if (Annotations::propertyHasAnnotation($entity, $property->name, "@manyToMany")) {
- $this->_fetchManyToMany($entity, $property->name);
- } elseif (Annotations::propertyHasAnnotation($entity, $property->name, "@oneToMany")) {
- $this->_fetchOneToMany($entity, $property->name);
- } elseif (Annotations::propertyHasAnnotation($entity, $property->name, "@manyToOne")) {
- $this->_fetchManyToOne($entity, $property->name);
- } elseif (Annotations::propertyHasAnnotation($entity, $property->name, "@oneToOne")) {
- $this->_fetchOneToOne($entity, $property->name);
- }
- }
- }
-
- return $entity;
- }
-}
+
+ * @copyright 2018 Aliens Group, Inc.
+ * @license MIT
+ * @version 1.0.0
+ * @link http://lightql.na2axl.tk
+ */
+
+namespace ElementaryFramework\LightQL\Sessions;
+
+use ElementaryFramework\Annotations\Annotations;
+use ElementaryFramework\Annotations\Exceptions\AnnotationException;
+use ElementaryFramework\LightQL\Annotations\EntityAnnotation;
+use ElementaryFramework\LightQL\Annotations\NamedQueryAnnotation;
+use ElementaryFramework\LightQL\Entities\EntitiesCollection;
+use ElementaryFramework\LightQL\Entities\Entity;
+use ElementaryFramework\LightQL\Entities\EntityManager;
+use ElementaryFramework\LightQL\Entities\IEntity;
+use ElementaryFramework\LightQL\Entities\IPrimaryKey;
+use ElementaryFramework\LightQL\Entities\IValueTransformer;
+use ElementaryFramework\LightQL\Entities\Query;
+use ElementaryFramework\LightQL\Exceptions\EntityException;
+use ElementaryFramework\LightQL\Exceptions\FacadeException;
+use ElementaryFramework\LightQL\Exceptions\LightQLException;
+use ElementaryFramework\LightQL\Exceptions\OperationCancelledException;
+use ElementaryFramework\LightQL\Exceptions\PersistenceUnitException;
+use ElementaryFramework\LightQL\Exceptions\ValueValidatorException;
+use ElementaryFramework\LightQL\Persistence\PersistenceUnit;
+
+/**
+ * Facade
+ *
+ * Base class for all entity facades.
+ *
+ * @abstract
+ * @category Sessions
+ * @package LightQL
+ * @author Nana Axel
+ * @link http://lightql.na2axl.tk/docs/api/LightQL/Sessions/Facade
+ */
+abstract class Facade implements IFacade
+{
+ /**
+ * The entity manager of this facade.
+ *
+ * @var EntityManager
+ */
+ protected $entityManager;
+
+ /**
+ * The entity class name managed by this facade.
+ *
+ * @var \ReflectionClass
+ */
+ private $_class;
+
+ /**
+ * The listener of this facade.
+ *
+ * @var IFacadeListener
+ */
+ private $_listener;
+
+ /**
+ * Facade constructor.
+ *
+ * @param string $class The entity class name managed by this facade.
+ *
+ * @throws AnnotationException When the Facade is unable to read an annotation.
+ * @throws EntityException When the entity class or object doesn't have an @entity annotation.
+ * @throws FacadeException When the entity class or object doesn't inherit from the Entity class.
+ * @throws LightQLException
+ * @throws PersistenceUnitException
+ * @throws \ReflectionException
+ */
+ public function __construct($class)
+ {
+ if (!Annotations::propertyHasAnnotation($this, "entityManager", "@persistenceUnit")) {
+ throw new FacadeException("Cannot create the entity facade. The property \"entityManager\" has no @persistenceUnit annotation.");
+ }
+
+ if (!Annotations::classHasAnnotation($class, "@entity")) {
+ throw new EntityException("Cannot create an entity without the @entity annotation.");
+ }
+
+ if (!is_subclass_of($class, Entity::class)) {
+ throw new FacadeException("Unable to create a facade. The entity class or object need to inherit directly from the class Entity.");
+ }
+
+ if (Annotations::classHasAnnotation($this, "@listener")) {
+ $listenerReflection = new \ReflectionClass(Annotations::ofClass($this, "@listener")[0]->listener);
+ $this->_listener = $listenerReflection->newInstance();
+ }
+
+ $this->_class = new \ReflectionClass($class);
+
+ $annotations = Annotations::ofProperty($this, "entityManager", "@persistenceUnit");
+ $this->entityManager = new EntityManager(PersistenceUnit::create($annotations[0]->name));
+ }
+
+ /**
+ * Creates an entity.
+ *
+ * @param IEntity $entity The entity to create.
+ *
+ * @throws AnnotationException
+ * @throws FacadeException When the facade is unable to create the entity.
+ * @throws OperationCancelledException When the operation has been cancelled by a listener
+ * @throws EntityException When the $entity object is not an instance of Entity class
+ */
+ public function create(IEntity &$entity)
+ {
+ if (!($entity instanceof Entity)) {
+ throw new EntityException("The Facade class works only with classes extending Entity.");
+ }
+
+ if (!$this->_class->isInstance($entity)) {
+ throw new FacadeException("Cannot create entity. The type of the entity is not valid for this facade.");
+ }
+
+ if ($this->_listener instanceof IFacadeListener && !$this->_listener->beforeCreate($entity)) {
+ throw new OperationCancelledException(Annotations::ofClass($this, "@listener")[0]->listener);
+ }
+
+ try {
+ $entity = $this->_parseRawEntity(
+ $this->entityManager->persist($entity),
+ $this->getEntityClassName(),
+ Annotations::ofClass($this->getEntityClassName(), "@entity")
+ );
+
+ $this->_listener instanceof IFacadeListener && $this->_listener->onCreate($entity);
+ } catch (\Exception $e) {
+ throw new FacadeException("Unable to create the entity. See internal exception for more details.", 0, $e);
+ }
+ }
+
+ /**
+ * Edit an entity.
+ *
+ * @param IEntity $entity The entity to edit.
+ *
+ * @throws AnnotationException
+ * @throws EntityException
+ * @throws FacadeException When the facade is unable to edit the entity
+ * @throws OperationCancelledException When the operation has been cancelled by a listener
+ * @throws ValueValidatorException
+ * @throws \ReflectionException
+ */
+ public function edit(IEntity &$entity)
+ {
+ if (!($entity instanceof Entity)) {
+ throw new EntityException("The Facade class works only with classes extending Entity.");
+ }
+
+ if (!$this->_class->isInstance($entity)) {
+ throw new FacadeException("Cannot edit entity. The type of the entity is not valid for this facade.");
+ }
+
+ if ($this->_listener instanceof IFacadeListener && !$this->_listener->beforeEdit($entity)) {
+ throw new OperationCancelledException(Annotations::ofClass($this, "@listener")[0]->listener);
+ }
+
+ $this->entityManager->merge($entity);
+
+ $this->_listener instanceof IFacadeListener && $this->_listener->onEdit($entity);
+ }
+
+ /**
+ * Delete an entity.
+ *
+ * @param IEntity $entity The entity to delete.
+ *
+ * @throws AnnotationException
+ * @throws EntityException
+ * @throws FacadeException When the facade is unable to delete the entity
+ * @throws OperationCancelledException When the operation has been cancelled by a listener
+ * @throws ValueValidatorException
+ * @throws \ReflectionException
+ */
+ public function delete(IEntity &$entity)
+ {
+ if (!($entity instanceof Entity)) {
+ throw new EntityException("The Facade class works only with classes extending Entity.");
+ }
+
+ if (!$this->_class->isInstance($entity)) {
+ throw new FacadeException("Cannot delete entity. The type of the entity is not valid for this facade.");
+ }
+
+ if ($this->_listener instanceof IFacadeListener && !$this->_listener->beforeDelete($entity)) {
+ throw new OperationCancelledException(Annotations::ofClass($this, "@listener")[0]->listener);
+ }
+
+ $this->entityManager->merge($entity);
+ $this->entityManager->delete($entity);
+
+ $this->_listener instanceof IFacadeListener && $this->_listener->onDelete($entity);
+ }
+
+ /**
+ * Find an entity.
+ *
+ * @param mixed $id The id of the entity to find
+ *
+ * @return IEntity|null
+ *
+ * @throws AnnotationException
+ * @throws EntityException
+ * @throws LightQLException
+ * @throws \ReflectionException
+ */
+ public function find($id): ?IEntity
+ {
+ return $this->_parseRawEntity(
+ $this->entityManager->find($this->getEntityClassName(), $id),
+ $this->getEntityClassName(),
+ Annotations::ofClass($this->getEntityClassName(), "@entity")
+ );
+ }
+
+ /**
+ * Find all entities.
+ *
+ * @return Entity[]
+ *
+ * @throws AnnotationException
+ * @throws EntityException
+ * @throws LightQLException
+ * @throws \ReflectionException
+ */
+ public function findAll(): EntitiesCollection
+ {
+ $annotations = Annotations::ofClass($this->getEntityClassName(), "@entity");
+
+ $rawEntities = $this->entityManager
+ ->getLightQL()
+ ->from($annotations[0]->table)
+ ->selectArray();
+
+ return new EntitiesCollection(
+ $this->_parseRawEntities(
+ $rawEntities,
+ $this->getEntityClassName(),
+ $annotations
+ )
+ );
+ }
+
+ /**
+ * Find all entities in the given range.
+ *
+ * @param int $start The starting offset.
+ * @param int $length The number of entities to find.
+ *
+ * @return Entity[]
+ *
+ * @throws AnnotationException
+ * @throws EntityException
+ * @throws LightQLException
+ * @throws \ReflectionException
+ */
+ public function findRange(int $start, int $length): EntitiesCollection
+ {
+ $annotations = Annotations::ofClass($this->getEntityClassName(), "@entity");
+
+ $rawEntities = $this->entityManager
+ ->getLightQL()
+ ->from($annotations[0]->table)
+ ->limit($start, $length)
+ ->selectArray();
+
+ return new EntitiesCollection(
+ $this->_parseRawEntities(
+ $rawEntities,
+ $this->getEntityClassName(),
+ $annotations
+ )
+ );
+ }
+
+ /**
+ * Count the number of entities.
+ *
+ * @return int
+ *
+ * @throws AnnotationException
+ * @throws LightQLException
+ */
+ public function count(): int
+ {
+ $annotations = Annotations::ofClass($this->getEntityClassName(), "@entity");
+
+ return $this->entityManager
+ ->getLightQL()
+ ->from($annotations[0]->table)
+ ->count();
+ }
+
+ /**
+ * Returns the entity class name of this facade.
+ *
+ * @return string
+ */
+ public function getEntityClassName(): string
+ {
+ return $this->_class->getName();
+ }
+
+ /**
+ * Returns the entity manager for this facade.
+ *
+ * @return EntityManager
+ */
+ public function getEntityManager(): EntityManager
+ {
+ return $this->entityManager;
+ }
+
+ /**
+ * Get a named query.
+ *
+ * @param string $name The name of the query.
+ *
+ * @return Query
+ *
+ * @throws FacadeException
+ * @throws AnnotationException
+ */
+ public function getNamedQuery(string $name): Query
+ {
+ if (!Annotations::classHasAnnotation($this->_class->name, "@namedQuery")) {
+ throw new FacadeException("The {$this->_class->name} has no @namedQuery annotation.");
+ }
+
+ $namedQueries = Annotations::ofClass($this->getEntityClassName(), "@namedQuery");
+ $query = null;
+
+ /** @var NamedQueryAnnotation $namedQuery */
+ foreach ($namedQueries as $namedQuery) {
+ if ($namedQuery->name === $name) {
+ $query = $namedQuery->query;
+ break;
+ }
+ }
+
+ if ($query === null) {
+ throw new FacadeException("The {$this->_class->name} has no @namedQuery annotation with the name {$name}.");
+ }
+
+ $q = new Query($this);
+ $q->setQuery($query);
+
+ return $q;
+ }
+
+ /**
+ * Fetch data for a many-to-many relation.
+ *
+ * @param IEntity $entity The managed entity.
+ * @param string $property The property in many-to-many relation.
+ *
+ * @throws EntityException
+ * @throws AnnotationException
+ * @throws LightQLException
+ */
+ private function _fetchManyToMany(&$entity, $property)
+ {
+ $manyToMany = Annotations::ofProperty($entity, $property, "@manyToMany");
+ $column = Annotations::ofProperty($entity, $property, "@column");
+
+ $mappedPropertyName = null;
+
+ /** @var Entity $referencedEntity */
+ $referencedEntity = new $manyToMany[0]->entity;
+ foreach ($referencedEntity->getColumns() as $p => $c) {
+ if ($c->isManyToMany) {
+ $mappedManyToMany = Annotations::ofProperty($referencedEntity, $p, "@manyToMany");
+ if ($mappedManyToMany[0]->crossTable === $manyToMany[0]->crossTable) {
+ $mappedPropertyName = $p;
+ break;
+ }
+ }
+ }
+ unset($referencedEntity);
+
+ if ($mappedPropertyName === null) {
+ throw new EntityException("Unable to find a suitable property with a @manyToMany annotation in the entity \"{$manyToMany[0]->entity}\".");
+ }
+
+ $mappedPropertyManyToManyAnnotation = Annotations::ofProperty($manyToMany[0]->entity, $mappedPropertyName, "@manyToMany");
+ $mappedPropertyColumnAnnotation = Annotations::ofProperty($manyToMany[0]->entity, $mappedPropertyName, "@column");
+ $referencedEntityAnnotations = Annotations::ofClass($manyToMany[0]->entity, "@entity");
+
+ $lightql = $this->entityManager->getLightQL();
+
+ $results = $lightql
+ ->from($manyToMany[0]->crossTable)
+ ->where(array("{$manyToMany[0]->crossTable}.{$manyToMany[0]->referencedColumn}" => $lightql->parseValue($entity->get($column[0]->name))))
+ ->joinArray(
+ "{$referencedEntityAnnotations[0]->table}.*",
+ array(
+ array(
+ "side" => "LEFT",
+ "table" => $referencedEntityAnnotations[0]->table,
+ "cond" => "{$manyToMany[0]->crossTable}.{$mappedPropertyManyToManyAnnotation[0]->referencedColumn} = {$referencedEntityAnnotations[0]->table}.{$mappedPropertyColumnAnnotation[0]->name}"
+ )
+ )
+ );
+
+ $entity->{$property} = $this->_parseRawEntities(
+ $results,
+ $manyToMany[0]->entity,
+ $referencedEntityAnnotations
+ );
+ }
+
+ /**
+ * Fetch data for a one-to-many relation.
+ *
+ * @param IEntity $entity The managed entity.
+ * @param string $property The property in one-to-many relation.
+ *
+ * @throws AnnotationException
+ * @throws EntityException
+ * @throws LightQLException
+ */
+ private function _fetchOneToMany(&$entity, $property)
+ {
+ $oneToMany = Annotations::ofProperty($entity, $property, "@oneToMany");
+ $column = Annotations::ofProperty($entity, $property, "@column");
+ $referencedEntityAnnotations = Annotations::ofClass($oneToMany[0]->entity, "@entity");
+
+ $mappedPropertyName = $this->_resolveMappedPropertyName($oneToMany[0]->entity, "ManyToOne", $oneToMany[0]->referencedColumn);
+
+ if ($mappedPropertyName === null) {
+ throw new EntityException("Unable to find a suitable property with @manyToOne annotation in the entity \"{$oneToMany[0]->entity}\".");
+ }
+
+ $lightql = $this->entityManager->getLightQL();
+
+ $result = $lightql
+ ->from($referencedEntityAnnotations[0]->table)
+ ->where(array("{$referencedEntityAnnotations[0]->table}.{$oneToMany[0]->referencedColumn}" => $lightql->parseValue($entity->get($column[0]->name))))
+ ->selectFirst("{$referencedEntityAnnotations[0]->table}.*");
+
+ $className = $oneToMany[0]->entity;
+
+ $entity->{$property} = $result;
+
+ if ($result !== null) {
+ $entity->{$property} = $this->_parseRawEntity(
+ $result,
+ $oneToMany[0]->entity,
+ $referencedEntityAnnotations
+ );
+ }
+ }
+
+ /**
+ * Fetch data for a many-to-one relation.
+ *
+ * @param IEntity $entity The managed entity.
+ * @param string $property The property in many-to-one relation.
+ *
+ * @throws AnnotationException
+ * @throws EntityException
+ * @throws LightQLException
+ */
+ private function _fetchManyToOne(&$entity, $property)
+ {
+ $manyToOne = Annotations::ofProperty($entity, $property, "@manyToOne");
+ $column = Annotations::ofProperty($entity, $property, "@column");
+ $referencedEntityAnnotations = Annotations::ofClass($manyToOne[0]->entity, "@entity");
+
+ $mappedPropertyName = $this->_resolveMappedPropertyName($manyToOne[0]->entity, "OneToMany", $manyToOne[0]->referencedColumn);
+
+ if ($mappedPropertyName === null) {
+ throw new EntityException("Unable to find a suitable property with @oneToMany annotation in the entity \"{$manyToOne[0]->entity}\".");
+ }
+
+ $lightql = $this->entityManager->getLightQL();
+
+ $results = $lightql
+ ->from($referencedEntityAnnotations[0]->table)
+ ->where(array("{$referencedEntityAnnotations[0]->table}.{$manyToOne[0]->referencedColumn}" => $lightql->parseValue($entity->get($column[0]->name))))
+ ->selectArray("{$referencedEntityAnnotations[0]->table}.*");
+
+ $className = $manyToOne[0]->entity;
+
+ $entity->{$property} = array_map(function($item) use ($entity, $mappedPropertyName, $className) {
+ $e = new $className($item);
+ $e->{$mappedPropertyName} = &$entity;
+ return $e;
+ }, $results);
+ }
+
+ /**
+ * Fetch data for a one-to-one relation.
+ *
+ * @param IEntity $entity The managed entity.
+ * @param string $property The property in one-to-one relation.
+ *
+ * @throws AnnotationException
+ * @throws EntityException
+ * @throws LightQLException
+ */
+ private function _fetchOneToOne(&$entity, $property)
+ {
+ $oneToOne = Annotations::ofProperty($entity, $property, "@oneToOne");
+ $column = Annotations::ofProperty($entity, $property, "@column");
+ $referencedEntityAnnotations = Annotations::ofClass($oneToOne[0]->entity, "@entity");
+
+ $mappedPropertyName = $this->_resolveMappedPropertyName($oneToOne[0]->entity, "OneToOne", $oneToOne[0]->referencedColumn);
+
+ if ($mappedPropertyName === null) {
+ throw new EntityException("Unable to find a suitable property with @oneToOne annotation in the entity \"{$oneToOne[0]->entity}\".");
+ }
+
+ $lightql = $this->entityManager->getLightQL();
+
+ $result = $lightql
+ ->from($referencedEntityAnnotations[0]->table)
+ ->where(array("{$referencedEntityAnnotations[0]->table}.{$oneToOne[0]->referencedColumn}" => $lightql->parseValue($entity->get($column[0]->name))))
+ ->selectFirst("{$referencedEntityAnnotations[0]->table}.*");
+
+ $className = $oneToOne[0]->entity;
+
+ $entity->{$property} = $result;
+
+ if ($result !== null) {
+ $entity->{$property} = $this->_parseRawEntity($result, $className, $referencedEntityAnnotations);
+ }
+ }
+
+ /**
+ * Resolve the name of a property mapped by an annotation.
+ *
+ * @param string $entityClass The class name of the mapped property.
+ * @param string $check The type of annotation to find.
+ * @param string $column The mapped column name.
+ *
+ * @return string|null
+ */
+ private function _resolveMappedPropertyName(string $entityClass, string $check, string $column): ?string
+ {
+ $mappedPropertyName = null;
+
+ /** @var Entity $referencedEntity */
+ $referencedEntity = new $entityClass;
+ foreach ($referencedEntity->getColumns() as $p => $c) {
+ if ($c->{"is{$check}"} && $c->getName() === $column) {
+ $mappedPropertyName = $p;
+ break;
+ }
+ }
+ unset($referencedEntity);
+
+ return $mappedPropertyName;
+ }
+
+ /**
+ * Parse a set of raw data to a set of Entities.
+ *
+ * @param array $rawEntities The set of raw entities data provided fromm database.
+ * @param string $className The name of the entity class.
+ * @param array $annotations The set of entity annotations.
+ *
+ * @return Entity[]
+ *
+ * @throws AnnotationException
+ * @throws EntityException
+ * @throws LightQLException
+ * @throws \ReflectionException
+ *
+ * @internal This method is intended to be used only internally by LightQL itself.
+ */
+ public function _parseRawEntities($rawEntities, $className, $annotations): array
+ {
+ $entities = array();
+
+ foreach ($rawEntities as $rawEntity) {
+ array_push($entities, $this->_parseRawEntity($rawEntity, $className, $annotations));
+ }
+
+ return $entities;
+ }
+
+ /**
+ * Parses raw data to Entity.
+ *
+ * @param array|null $rawEntity Raw entity data provided from database.
+ * @param string $className The name of the entity class.
+ * @param EntityAnnotation[] $annotations The set of entity annotations.
+ *
+ * @return Entity|null
+ *
+ * @throws AnnotationException
+ * @throws EntityException
+ * @throws LightQLException
+ * @throws \ReflectionException
+ */
+ private function _parseRawEntity($rawEntity, $className, $annotations): ?Entity
+ {
+ if ($rawEntity === null)
+ return null;
+
+ /** @var IPrimaryKey $pkClass */
+ $pkClassReflection = null;
+ $pkClass = null;
+
+ if (Annotations::classHasAnnotation($className, "@pkClass")) {
+ $pkClassAnnotation = Annotations::ofClass($className, "@pkClass");
+
+ if (\is_subclass_of($pkClassAnnotation[0]->name, IPrimaryKey::class)) {
+ $pkClassReflection = new \ReflectionClass($pkClassAnnotation[0]->name);
+
+ $pkClass = $pkClassReflection->newInstance();
+ } else {
+ throw new EntityException("The primary key class of this entity doesn't implement the IPrimaryKey interface");
+ }
+ }
+
+ if ($pkClass !== null) {
+ $properties = $pkClassReflection->getProperties(T_PUBLIC);
+
+ foreach ($properties as $property) {
+ if (Annotations::propertyHasAnnotation($pkClass, $property->name, "@column")) {
+ $columnAnnotations = Annotations::ofProperty($pkClass, $property->name, "@column");
+ $pkClass->{$property->name} = $rawEntity[$columnAnnotations[0]->name];
+ }
+ }
+ }
+
+ $reflection = new \ReflectionClass($className);
+
+ /** @var Entity $entity */
+ $entity = $reflection->newInstance($rawEntity);
+
+ if ($annotations[0]->fetchMode === Entity::FETCH_EAGER) {
+ $properties = $reflection->getProperties(T_PUBLIC);
+
+ foreach ($properties as $property) {
+ if (Annotations::propertyHasAnnotation($entity, $property->name, "@id") && $pkClass !== null) {
+ $entity->{$property->name} = $pkClass;
+ } elseif (Annotations::propertyHasAnnotation($entity, $property->name, "@manyToMany") && !is_array($property->getValue($entity))) {
+ $this->_fetchManyToMany($entity, $property->name);
+ } elseif (Annotations::propertyHasAnnotation($entity, $property->name, "@oneToMany") && !($property->getValue($entity) instanceof IEntity)) {
+ $this->_fetchOneToMany($entity, $property->name);
+ } elseif (Annotations::propertyHasAnnotation($entity, $property->name, "@manyToOne") && !is_array($property->getValue($entity))) {
+ $this->_fetchManyToOne($entity, $property->name);
+ } elseif (Annotations::propertyHasAnnotation($entity, $property->name, "@oneToOne") && !($property->getValue($entity) instanceof IEntity)) {
+ $this->_fetchOneToOne($entity, $property->name);
+ }
+ }
+ }
+
+ return $entity;
+ }
+}
diff --git a/src/LightQL/Sessions/GenericFacade.php b/src/LightQL/Sessions/GenericFacade.php
new file mode 100644
index 0000000..7a7ce58
--- /dev/null
+++ b/src/LightQL/Sessions/GenericFacade.php
@@ -0,0 +1,322 @@
+
+ * @copyright 2018 Aliens Group, Inc.
+ * @license MIT
+ * @version 1.0.0
+ * @link http://lightql.na2axl.tk
+ */
+
+namespace ElementaryFramework\LightQL\Sessions;
+
+use ElementaryFramework\LightQL\Entities\EntitiesCollection;
+use ElementaryFramework\LightQL\Entities\GenericEntity;
+use ElementaryFramework\LightQL\Entities\IEntity;
+use ElementaryFramework\LightQL\Exceptions\EntityException;
+use ElementaryFramework\LightQL\Exceptions\FacadeException;
+use ElementaryFramework\LightQL\Exceptions\LightQLException;
+use ElementaryFramework\LightQL\LightQL;
+use ElementaryFramework\LightQL\Persistence\PersistenceUnit;
+
+final class GenericFacade implements IFacade
+{
+ /**
+ * The persistence unit used by this instance.
+ *
+ * @var PersistenceUnit
+ */
+ private $_persistenceUnit;
+
+ /**
+ * The managed LightQL instance.
+ *
+ * @var LightQL
+ */
+ private $_lightql;
+
+ /**
+ * GenericFacade constructor.
+ *
+ * @param PersistenceUnit $persistenceUnit The persistence unit to use with this GenericFacade.
+ *
+ * @throws LightQLException
+ */
+ public function __construct(PersistenceUnit $persistenceUnit)
+ {
+ // Save the persistence unit
+ $this->_persistenceUnit = $persistenceUnit;
+
+ // Create a LightQL instance
+ $this->_lightql = new LightQL(
+ array(
+ "dbms" => $this->_persistenceUnit->getDbms(),
+ "database" => $this->_persistenceUnit->getDatabase(),
+ "hostname" => $this->_persistenceUnit->getHostname(),
+ "username" => $this->_persistenceUnit->getUsername(),
+ "password" => $this->_persistenceUnit->getPassword(),
+ "port" => $this->_persistenceUnit->getPort()
+ )
+ );
+ }
+
+ /**
+ * Creates an entity.
+ *
+ * @param IEntity $entity The entity to create.
+ *
+ * @throws FacadeException
+ * @throws EntityException
+ */
+ public function create(IEntity &$entity)
+ {
+ if ($entity instanceof GenericEntity) {
+ $this->_lightql->beginTransaction();
+ try {
+ $this->_lightql
+ ->from($entity->getName())
+ ->insert($entity->getData());
+
+ $this->_lightql->commit();
+ } catch (\Exception $e) {
+ $this->_lightql->rollback();
+
+ throw new EntityException($e->getMessage());
+ }
+ } else {
+ throw new FacadeException("Only GenericEntity instances can be used with GenericFacade.");
+ }
+ }
+
+ /**
+ * Edit an entity.
+ *
+ * @param IEntity $entity The entity to edit.
+ * @throws EntityException
+ * @throws FacadeException
+ */
+ public function edit(IEntity &$entity)
+ {
+ if ($entity instanceof GenericEntity) {
+ $this->_lightql->beginTransaction();
+ try {
+ $this->_lightql
+ ->from($entity->getName())
+ ->where(array(
+ $entity->getPk() => $this->_lightql->parseValue($entity->get($entity->getPk()))
+ ))
+ ->update($entity->getData());
+
+ $this->_lightql->commit();
+ } catch (\Exception $e) {
+ $this->_lightql->rollback();
+
+ throw new EntityException($e->getMessage());
+ }
+ } else {
+ throw new FacadeException("Only GenericEntity instances can be used with GenericFacade.");
+ }
+ }
+
+ /**
+ * Delete an entity.
+ *
+ * @param IEntity $entity The entity to delete.
+ * @throws EntityException
+ * @throws FacadeException
+ */
+ function delete(IEntity &$entity)
+ {
+ if ($entity instanceof GenericEntity) {
+ $this->_lightql->beginTransaction();
+ try {
+ $this->_lightql
+ ->from($entity->getName())
+ ->where(array(
+ $entity->getPk() => $this->_lightql->parseValue($entity->get($entity->getPk()))
+ ))
+ ->delete();
+
+ $this->_lightql->commit();
+ } catch (\Exception $e) {
+ $this->_lightql->rollback();
+
+ throw new EntityException($e->getMessage());
+ }
+ } else {
+ throw new FacadeException("Only GenericEntity instances can be used with GenericFacade.");
+ }
+ }
+
+ /**
+ * Find an entity.
+ *
+ * This method is unavailable. Use findGeneric instead.
+ *
+ * @param mixed $id The id of the entity to find
+ *
+ * @return IEntity|null
+ *
+ * @throws FacadeException
+ */
+ public function find($id): ?IEntity
+ {
+ throw new FacadeException("The \"find\" method is unavailable in GenericFacade, use \"findGeneric\" instead.");
+ }
+
+ /**
+ * Find an entity.
+ *
+ * @param string $table The name of the table
+ * @param string $pk The name of the column with primary key property
+ * @param mixed $id The pk value of the entity to find
+ *
+ * @return IEntity|null
+ *
+ * @throws LightQLException
+ */
+ public function findGeneric(string $table, string $pk, $id): ?IEntity
+ {
+ $raw = $this->_lightql
+ ->from($table)
+ ->where(array($pk => $this->_lightql->parseValue($id)))
+ ->selectFirst();
+
+ if ($raw === null)
+ return null;
+
+ return new GenericEntity($table, $pk, $raw);
+ }
+
+ /**
+ * Find all entities.
+ *
+ * This method is unavailable. Use findGeneric instead.
+ *
+ * @return EntitiesCollection
+ *
+ * @throws FacadeException
+ */
+ public function findAll(): EntitiesCollection
+ {
+ throw new FacadeException("The \"findAll\" method is unavailable in GenericFacade, use \"findAllGeneric\" instead.");
+ }
+
+ /**
+ * Find all entities.
+ *
+ * @param string $table The name of the table
+ * @param string $pk The name of the column with primary key property
+ *
+ * @return EntitiesCollection
+ *
+ * @throws LightQLException
+ */
+ public function findAllGeneric(string $table, string $pk): EntitiesCollection
+ {
+ $rawEntities = $this->_lightql
+ ->from($table)
+ ->selectArray();
+
+ return new EntitiesCollection(
+ array_map(function ($raw) use ($table, $pk) {
+ return new GenericEntity($table, $pk, $raw);
+ }, $rawEntities)
+ );
+ }
+
+ /**
+ * Find all entities in the given range.
+ *
+ * This method is unavailable. Use findRangeGeneric instead.
+ *
+ * @param int $start The starting offset.
+ * @param int $length The number of entities to find.
+ *
+ * @return EntitiesCollection
+ *
+ * @throws FacadeException
+ */
+ public function findRange(int $start, int $length): EntitiesCollection
+ {
+ throw new FacadeException("The \"findRange\" method is unavailable in GenericFacade, use \"findRangeGeneric\" instead.");
+ }
+
+ /**
+ * Find all entities in the given range.
+ *
+ * @param string $table The name of the table
+ * @param string $pk The name of the column with primary key property
+ * @param int $start The starting offset.
+ * @param int $length The number of entities to find.
+ *
+ * @return EntitiesCollection
+ *
+ * @throws LightQLException
+ */
+ public function findRangeGeneric(string $table, string $pk, int $start, int $length): EntitiesCollection
+ {
+ $rawEntities = $this->_lightql
+ ->from($table)
+ ->limit($start, $length)
+ ->selectArray();
+
+ return new EntitiesCollection(
+ array_map(function ($raw) use ($table, $pk) {
+ return new GenericEntity($table, $pk, $raw);
+ }, $rawEntities)
+ );
+ }
+
+ /**
+ * Count the number of entities.
+ *
+ * This method is unavailable. Use countGeneric instead.
+ *
+ * @return int
+ *
+ * @throws FacadeException
+ */
+ public function count(): int
+ {
+ throw new FacadeException("The \"count\" method is unavailable in GenericFacade, use \"countGeneric\" instead.");
+ }
+
+ /**
+ * Count the number of entities.
+ *
+ * @param string $table The name of the table
+ *
+ * @return int
+ *
+ * @throws LightQLException
+ */
+ public function countGeneric(string $table): int
+ {
+ return $this->_lightql
+ ->from($table)
+ ->count();
+ }
+}
\ No newline at end of file
diff --git a/src/LightQL/Sessions/IFacade.php b/src/LightQL/Sessions/IFacade.php
index 9f6f2db..789eb5a 100644
--- a/src/LightQL/Sessions/IFacade.php
+++ b/src/LightQL/Sessions/IFacade.php
@@ -32,7 +32,8 @@
namespace ElementaryFramework\LightQL\Sessions;
-use ElementaryFramework\LightQL\Entities\Entity;
+use ElementaryFramework\LightQL\Entities\EntitiesCollection;
+use ElementaryFramework\LightQL\Entities\IEntity;
/**
* IFacade
@@ -49,39 +50,39 @@ interface IFacade
/**
* Creates an entity.
*
- * @param Entity $entity The entity to create.
+ * @param IEntity $entity The entity to create.
*/
- function create(Entity &$entity);
+ function create(IEntity &$entity);
/**
* Edit an entity.
*
- * @param Entity $entity The entity to edit.
+ * @param IEntity $entity The entity to edit.
*/
- function edit(Entity &$entity);
+ function edit(IEntity &$entity);
/**
* Delete an entity.
*
- * @param Entity $entity The entity to delete.
+ * @param IEntity $entity The entity to delete.
*/
- function delete(Entity &$entity);
+ function delete(IEntity &$entity);
/**
* Find an entity.
*
* @param mixed $id The id of the entity to find
*
- * @return Entity
+ * @return IEntity|null
*/
- function find($id): Entity;
+ function find($id): ?IEntity;
/**
* Find all entities.
*
- * @return Entity[]
+ * @return EntitiesCollection
*/
- function findAll(): array;
+ function findAll(): EntitiesCollection;
/**
* Find all entities in the given range.
@@ -89,9 +90,9 @@ function findAll(): array;
* @param int $start The starting offset.
* @param int $length The number of entities to find.
*
- * @return Entity[]
+ * @return EntitiesCollection
*/
- function findRange(int $start, int $length): array;
+ function findRange(int $start, int $length): EntitiesCollection;
/**
* Count the number of entities.
diff --git a/src/LightQL/Sessions/IFacadeListener.php b/src/LightQL/Sessions/IFacadeListener.php
new file mode 100644
index 0000000..78e0cec
--- /dev/null
+++ b/src/LightQL/Sessions/IFacadeListener.php
@@ -0,0 +1,96 @@
+
+ * @copyright 2018 Aliens Group, Inc.
+ * @license MIT
+ * @version 1.0.0
+ * @link http://lightql.na2axl.tk
+ */
+
+namespace ElementaryFramework\LightQL\Sessions;
+
+use ElementaryFramework\LightQL\Entities\IEntity;
+
+/**
+ * IFacadeListener
+ *
+ * Provide methods for all entity facade listeners.
+ *
+ * @category Sessions
+ * @package LightQL
+ * @author Nana Axel
+ * @link http://lightql.na2axl.tk/docs/api/LightQL/Sessions/IFacadeListener
+ */
+interface IFacadeListener
+{
+ /**
+ * An entity will be created.
+ *
+ * @param IEntity $entity The entity to create.
+ *
+ * @return bool true if we can execute the query, false to cancel the entity creation.
+ */
+ function beforeCreate(IEntity &$entity): bool;
+
+ /**
+ * An entity was just created.
+ *
+ * @param IEntity $entity The created entity.
+ */
+ function onCreate(IEntity $entity);
+
+ /**
+ * An entity will be edited.
+ *
+ * @param IEntity $entity The entity to edit.
+ *
+ * @return bool true if we can execute the query, false to cancel the entity edition.
+ */
+ function beforeEdit(IEntity &$entity): bool;
+
+ /**
+ * An entity was just edited.
+ *
+ * @param IEntity $entity The entity to edit.
+ */
+ function onEdit(IEntity $entity);
+
+ /**
+ * An entity will be deleted.
+ *
+ * @param IEntity $entity The entity to delete.
+ *
+ * @return bool true if we can execute the query, false to cancel the entity deletion.
+ */
+ function beforeDelete(IEntity &$entity): bool;
+
+ /**
+ * An entity was just deleted.
+ *
+ * @param IEntity $entity The entity to delete.
+ */
+ function onDelete(IEntity $entity);
+}
\ No newline at end of file