Compare commits

..

No commits in common. "197eee90f72a6ba918208110df44d7613e9b733a" and "26febb631d2464fbd1d0dd8a57019538bda7cc18" have entirely different histories.

22 changed files with 36 additions and 369 deletions

View File

@ -1,95 +0,0 @@
# Ulmus
Welcome to the official Ulmus documentation.
## Quick start
Creating a simple user entity:
*/app/entity/user.php*
```php
<?php
namespace MyApp\Entity;
use Ulmus\Entity\Field\Datetime;
/**
* @Table('name' => "user")
*/
class User
{
use \Ulmus\EntityTrait;
/**
* @Id
*/
public int $id;
/**
* @Field
*/
public string $fullname;
/**
* @Field
*/
public ? string $email;
/**
* @Field('name' => 'is_admin')
*/
public bool $isAdmin = false;
/**
* @DateTime
*/
public Datetime $birthday;
}
```
### Loading a user using a Primary Key
With this entity, we could quickly load a user using the `@Id` field using:
```php
$user = Entity\User::repository()->loadFromPk((int) $_GET['id']);
```
Or we could also, load another single entity using a specific field:
```php
# fetch a single user
$user = Entity\User::repository()->loadOneFromField((string) $_POST['email'], 'email');
# Fetch every admins
$admin_list = Entity\User::repository()->loadFromField(true, 'isAdmin');
```
Using the same entity class, we could create a new user and save it using:
```php
$user = new Entity\User();
$user->fullname = "Johnny Does";
$user->birthday = "1980-01-15";
if ( Entity\User::repository()->save($user) ) {
echo "User created successfully !";
}
else {
echo "User could not be saved :\";
}
```
Which would result in the following query (from MySQL adapter) being generated and executed :
```SQL
INSERT INTO `user` VALUES fullname = :fullname, birthday = :birthday;
```
Binded using:
```PHP
'fullname' => "Johnny Does",
'birthday' => "1980-01-15",
```

View File

View File

View File

View File

View File

@ -19,8 +19,7 @@ interface AdapterInterface {
public function defaultEngine() : ? string; public function defaultEngine() : ? string;
public function writableValue(/* mixed */ $value); /*: mixed*/ public function writableValue(/* mixed */ $value); /*: mixed*/
/* public function databaseName() : string; /* public function databaseName() : string;
public function mapFieldType(FieldDefinition $field) : string; public function mapFieldType(FieldDefinition $field) : string;
public function schemaTable(string $databaseName, string $tableName) /*: object|EntityCollection public function schemaTable(string $databaseName, string $tableName) /*: object|EntityCollection

View File

@ -2,7 +2,7 @@
namespace Ulmus\Adapter; namespace Ulmus\Adapter;
use Ulmus\{ConnectionAdapter, Entity\InformationSchema\Table, Migration\FieldDefinition, Repository, QueryBuilder}; use Ulmus\{Entity\InformationSchema\Table, Migration\FieldDefinition, Repository, QueryBuilder};
trait DefaultAdapterTrait trait DefaultAdapterTrait
{ {
@ -30,9 +30,9 @@ trait DefaultAdapterTrait
return $this->database; return $this->database;
} }
public function schemaTable(ConnectionAdapter $parent, $databaseName, string $tableName) /* : ? object */ public function schemaTable(string $databaseName, string $tableName) /* : ? object */
{ {
return Table::repository(Repository::DEFAULT_ALIAS, $parent)->where($this->escapeIdentifier('table_schema', AdapterInterface::IDENTIFIER_FIELD), $databaseName)->loadOneFromField($this->escapeIdentifier('table_name', AdapterInterface::IDENTIFIER_FIELD), $tableName); return Table::repository()->where(Table::field('schema'), $databaseName)->loadOneFromField(Table::field('name'), $tableName);
} }
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string

View File

@ -83,9 +83,6 @@ class MsSQL implements AdapterInterface {
catch(PDOException $ex){ catch(PDOException $ex){
throw $ex; throw $ex;
} }
finally {
$this->password = str_repeat('*', random_int(8,16));
}
return $pdo; return $pdo;
} }
@ -215,12 +212,9 @@ class MsSQL implements AdapterInterface {
} }
public function writableValue(mixed $value) /*: mixed*/ public function writableValue(/* mixed */ $value) /*: mixed*/
{ {
switch (true) { switch (true) {
case $value instanceof \UnitEnum:
return Ulmus::convertEnum($value);
case is_object($value): case is_object($value):
return Ulmus::convertObject($value); return Ulmus::convertObject($value);

View File

@ -77,7 +77,7 @@ class MySQL implements AdapterInterface {
throw $ex; throw $ex;
} }
finally { finally {
$this->password = str_repeat('*', random_int(8,16)); $this->password = str_repeat('*', strlen($this->password));
} }
return $pdo; return $pdo;
@ -148,12 +148,9 @@ class MySQL implements AdapterInterface {
} }
} }
public function writableValue(mixed $value) : mixed public function writableValue(/* mixed */ $value) /*: mixed*/
{ {
switch (true) { switch (true) {
case $value instanceof \UnitEnum:
return Ulmus::convertEnum($value);
case is_object($value): case is_object($value):
return Ulmus::convertObject($value); return Ulmus::convertObject($value);

View File

@ -87,7 +87,7 @@ class SQLite implements AdapterInterface {
return substr($base, 0, strrpos($base, '.') ?: strlen($base)); return substr($base, 0, strrpos($base, '.') ?: strlen($base));
} }
public function schemaTable(string $databaseName, string $tableName) : null|object public function schemaTable(string $databaseName, string $tableName) /* : ? object */
{ {
return Table::repository()->loadOneFromField(Table::field('tableName'), $tableName); return Table::repository()->loadOneFromField(Table::field('tableName'), $tableName);
} }
@ -131,12 +131,9 @@ class SQLite implements AdapterInterface {
return $typeOnly ? $type : $type . ( $length ? "($length" . ( isset($precision) ? ",$precision" : "" ) . ")" : "" ); return $typeOnly ? $type : $type . ( $length ? "($length" . ( isset($precision) ? ",$precision" : "" ) . ")" : "" );
} }
public function writableValue(mixed $value) /*: mixed*/ public function writableValue(/* mixed */ $value) /*: mixed*/
{ {
switch (true) { switch (true) {
case $value instanceof \UnitEnum:
return Ulmus::convertEnum($value);
case is_object($value): case is_object($value):
return Ulmus::convertObject($value); return Ulmus::convertObject($value);
@ -171,8 +168,6 @@ class SQLite implements AdapterInterface {
public function exportFunctions(PdoObject $pdo) : void public function exportFunctions(PdoObject $pdo) : void
{ {
$pdo->sqliteCreateFunction('if', fn($comparison, $yes, $no) => $comparison ? $yes : $no, 3);
$pdo->sqliteCreateFunction('length', fn($string) => strlen($string), 1);
$pdo->sqliteCreateFunction('lcase', fn($string) => strtolower($string), 1); $pdo->sqliteCreateFunction('lcase', fn($string) => strtolower($string), 1);
$pdo->sqliteCreateFunction('ucase', fn($string) => strtoupper($string), 1); $pdo->sqliteCreateFunction('ucase', fn($string) => strtoupper($string), 1);
$pdo->sqliteCreateFunction('left', fn($string, $length) => substr($string, 0, $length), 2); $pdo->sqliteCreateFunction('left', fn($string, $length) => substr($string, 0, $length), 2);
@ -194,4 +189,4 @@ class SQLite implements AdapterInterface {
return (int) in_array($string, explode(',', $string_list)); return (int) in_array($string, explode(',', $string_list));
}, 2); }, 2);
} }
} }

View File

@ -38,7 +38,7 @@ class ConnectionAdapter
$this->adapter->setup($connection); $this->adapter->setup($connection);
unset($this->configuration['connections']); unset($this->configuration['connections'][$this->name]);
} }
public function getConfiguration() : array public function getConfiguration() : array
@ -71,7 +71,7 @@ class ConnectionAdapter
{ {
return $this->pdo(); return $this->pdo();
} }
/** /**
* Instanciate an adapter which interact with the data source * Instanciate an adapter which interact with the data source
* @param string $name An Ulmus adapter or full class name implementing AdapterInterface * @param string $name An Ulmus adapter or full class name implementing AdapterInterface

View File

@ -27,16 +27,7 @@ class ObjectInstanciator {
return (string) $obj; return (string) $obj;
} }
public function enum(\UnitEnum $obj)
{
if (! $obj instanceof \BackedEnum) {
throw new \Ulmus\Exception\BackedEnumRequired("Unable to extract a UnitEnum value from this variable. You must define your enum as a BackedEnum instead of an UnitEnum.");
}
return $obj->value;
}
public function registerObject(string $type, Callable $callback) : void public function registerObject(string $type, Callable $callback) : void
{ {
$this->objectCallbackDefinition[$type] = $callback; $this->objectCallbackDefinition[$type] = $callback;

View File

@ -1,48 +0,0 @@
<?php
namespace Ulmus\Entity\Sqlite;
use Ulmus\EntityCollection;
/**
* @Table
*/
class Column
{
use \Ulmus\EntityTrait;
/**
* @Id
*/
public int $cid;
/**
* @Field
*/
public string $type;
/**
* @Field
*/
public string $name;
/**
* @Virtual
*/
public string $tableName;
/**
* @Field('name' => "notnull")
*/
public bool $notNull;
/**
* @Field('name' => 'dflt_value')
*/
public ? string $defaultValue;
/**
* @Field('name' => "pk")
*/
public bool $primaryKey;
}

View File

@ -2,14 +2,13 @@
namespace Ulmus\Entity\Sqlite; namespace Ulmus\Entity\Sqlite;
use Ulmus\ConnectionAdapter;
use Ulmus\Repository; use Ulmus\Repository;
class Table extends Schema class Table extends Schema
{ {
public static function repository(string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null): Repository public static function repository(string $alias = Repository::DEFAULT_ALIAS): Repository
{ {
return new class(static::class, $alias, $adapter) extends Repository\SqliteRepository return new class(static::class, $alias) extends Repository\SqliteRepository
{ {
public function finalizeQuery(): void public function finalizeQuery(): void
{ {

View File

@ -78,19 +78,11 @@ trait EntityTrait {
$this->{$field['name']} = $value; $this->{$field['name']} = $value;
} }
elseif ( $value instanceof \UnitEnum ) {
$this->{$field['name']} = $value;
}
elseif (enum_exists($field['type'])) {
$this->{$field['name']} = $field['type']::from($value);
}
elseif ( ! $field['builtin'] ) { elseif ( ! $field['builtin'] ) {
try { try {
$this->{$field['name']} = Ulmus::instanciateObject($field['type'], [ $value ]); $this->{$field['name']} = Ulmus::instanciateObject($field['type'], [ $value ]);
} }
catch(\Error $e) { catch(\Error $e) {
$f = $field['type'];
dump($f, $f::from($value));
throw new \Error(sprintf("%s for class '%s' on field '%s'", $e->getMessage(), get_class($this), $field['name'])); throw new \Error(sprintf("%s for class '%s' on field '%s'", $e->getMessage(), get_class($this), $field['name']));
} }
} }
@ -107,7 +99,7 @@ trait EntityTrait {
$this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER) + $this->entityLoadedDataset; $this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER) + $this->entityLoadedDataset;
} }
} }
return $this; return $this;
} }
@ -293,9 +285,9 @@ trait EntityTrait {
/** /**
* @Ignore * @Ignore
*/ */
public static function repository(string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) : Repository public static function repository(string $alias = Repository::DEFAULT_ALIAS) : Repository
{ {
return Ulmus::repository(static::class, $alias, $adapter); return Ulmus::repository(static::class, $alias);
} }
/** /**
@ -309,6 +301,7 @@ trait EntityTrait {
return $collection; return $collection;
} }
/** /**
* @Ignore * @Ignore
*/ */
@ -322,7 +315,6 @@ trait EntityTrait {
*/ */
public static function field($name, ? string $alias = Repository::DEFAULT_ALIAS) : EntityField public static function field($name, ? string $alias = Repository::DEFAULT_ALIAS) : EntityField
{ {
return new EntityField(static::class, $name, $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : Repository::DEFAULT_ALIAS, Ulmus::resolveEntity(static::class)); return new EntityField(static::class, $name, $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : Repository::DEFAULT_ALIAS, Ulmus::resolveEntity(static::class));
} }

View File

@ -1,5 +0,0 @@
<?php
namespace Ulmus\Exception;
class BackedEnumRequired extends \RuntimeException {}

View File

@ -1,45 +0,0 @@
<?php
namespace Ulmus\Query;
class Show extends Fragment {
public int $order = -100;
const SQL_TOKEN = "SHOW";
const SQL_TOKEN_FROM = "FROM";
const SQL_SHOW_DATABASES = "DATABASES";
const SQL_SHOW_TABLES = "TABLES";
public string $show;
public string $from;
public string $in;
public function set(string $show, ? string $from = null, ? string $in = null) : self
{
$this->show = $show;
if ( $from !== null ) {
$this->from = $from;
}
if ( $in !== null ) {
$this->in = $in;
}
return $this;
}
public function render() : string
{
return $this->renderSegments([
static::SQL_TOKEN, $this->show,
] + ( ! empty($this->from) ? [ static::SQL_TOKEN_FROM, $this->from ] : [] ) + ( ! empty($this->in) ? [ static::SQL_TOKEN_IN, $this->in ] : [] ) );
}
}

View File

@ -314,39 +314,6 @@ class QueryBuilder implements Query\QueryBuilderInterface
return $join; return $join;
} }
public function showDatabases() : self
{
$show = new Query\Show();
$this->push($show);
$show->set(Query\Show::SQL_SHOW_DATABASES);
return $this;
}
public function showTables(? string $database = null) : self
{
$show = new Query\Show();
$this->push($show);
$show->set(Query\Show::SQL_SHOW_TABLES, $database);
return $this;
}
public function showColumns(string $table) : self
{
$show = new Query\Show();
$this->push($show);
$show->set(Query\Show::SQL_SHOW_COLUMNS, $table);
return $this;
}
public function truncate(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self public function truncate(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self
{ {
if ( $schema ) { if ( $schema ) {

View File

@ -21,22 +21,4 @@ class SqliteQueryBuilder extends QueryBuilder implements Query\QueryBuilderInter
return $this; return $this;
} }
public function showColumns(string $table) : self
{
$this->pragma('table_info', $table, true);
return $this;
}
public function showTables(? string $database = null) : self
{
$show = new Query\Show();
$this->push($show);
$show->set(Query\Show::SQL_SHOW_TABLES, $database);
return $this;
}
} }

View File

@ -38,7 +38,7 @@ class Repository
public function loadOne() : ? object public function loadOne() : ? object
{ {
return $this->limit(1)->selectSqlQuery()->collectionFromQuery()[0] ?? null; return $this->limit(1)->collectionFromQuery()[0] ?? null;
} }
public function loadOneFromField($field, $value) : ? object public function loadOneFromField($field, $value) : ? object
@ -53,7 +53,7 @@ class Repository
public function loadAll() : EntityCollection public function loadAll() : EntityCollection
{ {
return $this->selectSqlQuery()->collectionFromQuery(); return $this->collectionFromQuery();
} }
public function loadAllFromField($field, $value, $operator= Query\Where::OPERATOR_EQUAL) : EntityCollection public function loadAllFromField($field, $value, $operator= Query\Where::OPERATOR_EQUAL) : EntityCollection
@ -63,7 +63,7 @@ class Repository
public function loadFromField($field, $value, $operator= Query\Where::OPERATOR_EQUAL) : EntityCollection public function loadFromField($field, $value, $operator= Query\Where::OPERATOR_EQUAL) : EntityCollection
{ {
return $this->selectSqlQuery()->where($field, $value, $operator)->collectionFromQuery(); return $this->where($field, $value, $operator)->collectionFromQuery();
} }
public function count() : int public function count() : int
@ -278,7 +278,7 @@ class Repository
$this->finalizeQuery(); $this->finalizeQuery();
# ??? $result = Ulmus::runSelectQuery($this->queryBuilder, $this->adapter); $result = Ulmus::runSelectQuery($this->queryBuilder, $this->adapter);
return $this; return $this;
} }
@ -288,26 +288,12 @@ class Repository
return $this->createSqlQuery()->runQuery(); return $this->createSqlQuery()->runQuery();
} }
public function listTables(? string $database = null)
{
return $this->showTablesSqlQuery($database)->runQuery();
}
public function listColumns(? string $table = null)
{
$table ??= $this->entityResolver->tableName();
$this->showColumnsSqlQuery($table);
return $this->collectionFromQuery(Entity\InformationSchema\Column::class);
}
public function generateDatasetDiff(object $entity, bool $oldValues = false) : array public function generateDatasetDiff(object $entity, bool $oldValues = false) : array
{ {
$array = array_change_key_case($entity->toArray()); $array = array_change_key_case($entity->toArray());
$dataset = array_change_key_case($entity->entityGetDataset(false, true)); $dataset = array_change_key_case($entity->entityGetDataset(false, true));
# dump($array, $dataset);
return array_udiff_assoc($oldValues ? $dataset : $array , $oldValues ? $array : $dataset, function($e1, $e2) { return array_udiff_assoc($oldValues ? $dataset : $array , $oldValues ? $array : $dataset, function($e1, $e2) {
if ( is_array($e1) ) { if ( is_array($e1) ) {
if (is_array($e2)) { if (is_array($e2)) {
@ -598,9 +584,7 @@ class Repository
if ( null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], new RelationIgnore) ) { if ( null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], new RelationIgnore) ) {
$escAlias = $this->escapeIdentifier($alias); $escAlias = $this->escapeIdentifier($alias);
$name = $entity::resolveEntity()->searchFieldAnnotation($field['name'], new Field())->name ?? $field['name']; $this->select("$escAlias.$key as $alias\${$field['name']}");
$this->select("$escAlias.$key as $alias\${$name}");
} }
} }
@ -801,12 +785,14 @@ class Repository
public function collectionFromQuery(? string $entityClass = null) : EntityCollection public function collectionFromQuery(? string $entityClass = null) : EntityCollection
{ {
$class = $entityClass ?? $this->entityClass; $class = $entityClass ?: $this->entityClass;
$entityCollection = $class::entityCollection(); $entityCollection = $this->instanciateEntityCollection();
$this->selectSqlQuery();
$this->finalizeQuery(); $this->finalizeQuery();
foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) { foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) {
$entityCollection->append( ( new $class() )->resetVirtualProperties()->entityFillFromDataset($entityData) ); $entityCollection->append( ( new $class() )->resetVirtualProperties()->entityFillFromDataset($entityData) );
} }
@ -930,33 +916,6 @@ class Repository
return $this; return $this;
} }
public function showDatabasesSqlQuery() : self
{
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
$this->queryBuilder->showDatabases();
}
return $this;
}
public function showTablesSqlQuery() : self
{
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
$this->queryBuilder->showTables();
}
return $this;
}
public function showColumnsSqlQuery(string $table) : self
{
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
$this->queryBuilder->showColumns($table);
}
return $this;
}
protected function fromRow($row) : self protected function fromRow($row) : self
{ {

View File

@ -2,7 +2,7 @@
namespace Ulmus\Repository; namespace Ulmus\Repository;
use Ulmus\{ConnectionAdapter, QueryBuilder, Repository, Query, Ulmus, Entity}; use Ulmus\{ConnectionAdapter, QueryBuilder, Repository, Query, Ulmus};
class SqliteRepository extends Repository { class SqliteRepository extends Repository {
@ -18,15 +18,6 @@ class SqliteRepository extends Repository {
} }
public function listColumns(? string $table = null)
{
$table ??= $this->entityResolver->tableName();
$this->showColumnsSqlQuery($table);
return $this->collectionFromQuery(Entity\Sqlite\Column::class)->iterate(fn($e) => $e->tableName = $table);
}
protected function serverRequestCountRepository() : Repository protected function serverRequestCountRepository() : Repository
{ {
return new static($this->entityClass, $this->alias, $this->adapter); return new static($this->entityClass, $this->alias, $this->adapter);

View File

@ -107,12 +107,11 @@ abstract class Ulmus
return static::$resolved[$entityClass] ?? static::$resolved[$entityClass] = new Common\EntityResolver($entityClass); return static::$resolved[$entityClass] ?? static::$resolved[$entityClass] = new Common\EntityResolver($entityClass);
} }
public static function repository(string $entityClass, string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) : Repository public static function repository(string $entityClass, ...$arguments) : Repository
{ {
$adapter ??= $entityClass::resolveEntity()->sqlAdapter(); $cls = $entityClass::resolveEntity()->sqlAdapter()->adapter()->repositoryClass();
$cls = $adapter->adapter()->repositoryClass();
return new $cls($entityClass, $alias, $adapter); return new $cls($entityClass, ...$arguments);
} }
public static function queryBuilder($entityClass, ...$arguments) : Query\QueryBuilderInterface public static function queryBuilder($entityClass, ...$arguments) : Query\QueryBuilderInterface
@ -131,17 +130,12 @@ abstract class Ulmus
{ {
return ( static::$objectInstanciator ?? static::$objectInstanciator = new Entity\ObjectInstanciator() )->convert($obj); return ( static::$objectInstanciator ?? static::$objectInstanciator = new Entity\ObjectInstanciator() )->convert($obj);
} }
public static function convertEnum(\UnitEnum $enum)
{
return ( static::$objectInstanciator ?? static::$objectInstanciator = new Entity\ObjectInstanciator() )->enum($enum);
}
public static function encodeArray(array $array) public static function encodeArray(array $array)
{ {
return json_encode($array); return json_encode($array);
} }
public static function registerAdapter(ConnectionAdapter $adapter, bool $default = false) : void public static function registerAdapter(ConnectionAdapter $adapter, bool $default = false) : void
{ {
if ($default) { if ($default) {