Compare commits

...

2 Commits

Author SHA1 Message Date
Dave Mc Nicoll
8fff21ecb0 - Fixed RelationBuilder problem whenever a field wasn't set 2025-05-15 18:09:52 +00:00
Dave Mc Nicoll
cc6048d4ee - WIP on migration and some bug fixes linked to withJoin() select problems 2025-05-15 18:05:23 +00:00
20 changed files with 204 additions and 96 deletions

View File

@ -4,7 +4,7 @@ namespace Ulmus\Adapter;
use Ulmus\{ConnectionAdapter, Entity\InformationSchema\Table, Migration\FieldDefinition, Repository, QueryBuilder\Sql\MysqlQueryBuilder, Entity}; use Ulmus\{ConnectionAdapter, Entity\InformationSchema\Table, Migration\FieldDefinition, Repository, QueryBuilder\Sql\MysqlQueryBuilder, Entity};
trait DefaultAdapterTrait UNUSED? trait DefaultAdapterTrait
{ {
public function repositoryClass() : string public function repositoryClass() : string
{ {

View File

@ -9,10 +9,14 @@ use Ulmus\Exception\AdapterConfigurationException;
use Ulmus\Ulmus; use Ulmus\Ulmus;
use Ulmus\Migration\FieldDefinition; use Ulmus\Migration\FieldDefinition;
use Ulmus\{Entity\InformationSchema\Table, Migration\MigrateInterface, Repository, QueryBuilder}; use Ulmus\{Entity\InformationSchema\Table,
Migration\MigrateInterface,
Migration\SqlMigrationTrait,
Repository,
QueryBuilder};
class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface { class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
use SqlAdapterTrait; use SqlAdapterTrait, SqlMigrationTrait;
const ALLOWED_ATTRIBUTES = [ const ALLOWED_ATTRIBUTES = [
'default', 'primary_key', 'auto_increment', 'default', 'primary_key', 'auto_increment',
@ -205,7 +209,14 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
$this->traceOn = $configuration['trace_on']; $this->traceOn = $configuration['trace_on'];
} }
} }
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
{
$mapper = new MsSQLFieldMapper($field);
return $typeOnly ? $mapper->type : $mapper->render();
}
public static function escapeIdentifier(string $segment, int $type) : string public static function escapeIdentifier(string $segment, int $type) : string
{ {
switch($type) { switch($type) {

View File

@ -0,0 +1,29 @@
<?php
namespace Ulmus\Adapter;
use Ulmus\Migration\FieldDefinition;
class MsSQLFieldMapper extends SqlFieldMapper
{
public function map() : void
{
parent::map();
if (in_array($this->type, [ 'CHAR', 'VARCHAR', 'TEXT', ])) {
$this->type = "N" . $this->type;
};
}
/* @TODO !
public function postProcess() : void
{
if (
in_array($this->type, [ 'BLOB', 'TINYBLOB', 'MEDIUMBLOB', 'LONGBLOB', 'JSON', 'TEXT', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'GEOMETRY' ]) &&
! is_object($this->field->default ?? false) # Could be a functional default, which would now be valid
) {
unset($this->field->default);
}
}
* */
}

View File

@ -6,6 +6,7 @@ use Ulmus\ConnectionAdapter;
use Ulmus\Entity\Mysql\Table; use Ulmus\Entity\Mysql\Table;
use Ulmus\Migration\FieldDefinition; use Ulmus\Migration\FieldDefinition;
use Ulmus\Migration\MigrateInterface; use Ulmus\Migration\MigrateInterface;
use Ulmus\Migration\SqlMigrationTrait;
use Ulmus\QueryBuilder\Sql; use Ulmus\QueryBuilder\Sql;
use Ulmus\Common\PdoObject; use Ulmus\Common\PdoObject;
@ -13,7 +14,7 @@ use Ulmus\Exception\AdapterConfigurationException;
use Ulmus\Repository; use Ulmus\Repository;
class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface { class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
use SqlAdapterTrait; use SqlAdapterTrait, SqlMigrationTrait;
const ALLOWED_ATTRIBUTES = [ const ALLOWED_ATTRIBUTES = [
'default', 'primary_key', 'auto_increment', 'update', 'default', 'primary_key', 'auto_increment', 'update',

View File

@ -6,10 +6,6 @@ use Ulmus\Migration\FieldDefinition;
class MySQLFieldMapper extends SqlFieldMapper class MySQLFieldMapper extends SqlFieldMapper
{ {
public readonly string $type;
public readonly string $length;
public function map() : void public function map() : void
{ {
$type = $this->field->type; $type = $this->field->type;

View File

@ -7,10 +7,10 @@ use Ulmus\Common\PdoObject;
use Ulmus\ConnectionAdapter; use Ulmus\ConnectionAdapter;
use Ulmus\Entity; use Ulmus\Entity;
use Ulmus\Migration\FieldDefinition; use Ulmus\Migration\FieldDefinition;
use Ulmus\{Migration\MigrateInterface, Repository, QueryBuilder}; use Ulmus\{Migration\MigrateInterface, Migration\SqlMigrationTrait, Repository, QueryBuilder};
class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface { class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
use SqlAdapterTrait; use SqlAdapterTrait, SqlMigrationTrait;
const ALLOWED_ATTRIBUTES = [ const ALLOWED_ATTRIBUTES = [
'default', 'primary_key', 'auto_increment', 'collate nocase', 'collate binary', 'collate rtrim', 'default', 'primary_key', 'auto_increment', 'collate nocase', 'collate binary', 'collate rtrim',

View File

@ -36,44 +36,11 @@ trait SqlAdapterTrait
return $this->database; return $this->database;
} }
public function schemaTable(ConnectionAdapter $adapter, $databaseName, string $tableName) : null|object
{
return Table::repository(Repository::DEFAULT_ALIAS, $adapter)
->select(\Ulmus\Common\Sql::raw('this.*'))
->where($this->escapeIdentifier('table_schema', AdapterInterface::IDENTIFIER_FIELD), $databaseName)
->loadOneFromField($this->escapeIdentifier('table_name', AdapterInterface::IDENTIFIER_FIELD), $tableName);
}
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
{
$mapper = new SqlFieldMapper($field);
return $typeOnly ? $mapper->type : $mapper->render();
}
public function whitelistAttributes(array &$parameters) : void public function whitelistAttributes(array &$parameters) : void
{ {
$parameters = array_intersect_key($parameters, array_flip(static::ALLOWED_ATTRIBUTES)); $parameters = array_intersect_key($parameters, array_flip(static::ALLOWED_ATTRIBUTES));
} }
public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable
{
if (! empty($field['previous']) ) {
$position = sprintf('AFTER %s', $this->escapeIdentifier($field['previous']->field, AdapterInterface::IDENTIFIER_FIELD));
}
else {
$position = "FIRST";
}
return implode(" ", [
strtoupper($field['action']),
$this->escapeIdentifier($definition->getSqlName(), AdapterInterface::IDENTIFIER_FIELD),
$definition->getSqlType(),
$definition->getSqlParams(),
$position,
]);
}
public function writableValue(mixed $value) : mixed public function writableValue(mixed $value) : mixed
{ {
switch (true) { switch (true) {
@ -92,9 +59,4 @@ trait SqlAdapterTrait
return $value; return $value;
} }
public function splitAlterQuery() : bool
{
return false;
}
} }

View File

@ -7,9 +7,9 @@ use Ulmus\Entity;
class SqlFieldMapper class SqlFieldMapper
{ {
public readonly string $type; public string $type;
public readonly string $length; public string $length;
public function __construct( public function __construct(
public FieldDefinition $field, public FieldDefinition $field,

View File

@ -10,6 +10,12 @@ class Attribute
public static function handleArrayField(null|\Stringable|string|array $field, null|string|bool $alias = Repository::DEFAULT_ALIAS, string $separator = ', ') : mixed public static function handleArrayField(null|\Stringable|string|array $field, null|string|bool $alias = Repository::DEFAULT_ALIAS, string $separator = ', ') : mixed
{ {
if ( is_array($field) ) { if ( is_array($field) ) {
if (count($field) < 2) {
throw new \RuntimeException(
sprintf("Array field must be formed of at least two things, a class name, and a property. (received %s)", json_encode($field))
);
}
$class = array_shift($field); $class = array_shift($field);
$field[1] ??= $alias; $field[1] ??= $alias;

View File

@ -10,6 +10,7 @@ class Virtual extends Field implements ResettablePropertyInterface {
public function __construct( public function __construct(
public null|string|array $method = null, public null|string|array $method = null,
public ? \Closure $closure = null, public ? \Closure $closure = null,
public null|string $name = null,
) { ) {
$this->method ??= [ static::class, 'noop' ]; $this->method ??= [ static::class, 'noop' ];
} }

View File

@ -114,7 +114,12 @@ class EntityResolver {
throw new \InvalidArgumentException("Can't find entity relation's column named `$name` from entity {$this->entityClass}"); throw new \InvalidArgumentException("Can't find entity relation's column named `$name` from entity {$this->entityClass}");
} }
} }
public function getPropertyEntityType(string $name) : false|string
{
return $this->reflectedClass->getProperties(true)[$name]->getTypes()[0]->type ?? false;
}
public function searchFieldAnnotation(string $field, array|object|string $annotationType, bool $caseSensitive = true) : ? object public function searchFieldAnnotation(string $field, array|object|string $annotationType, bool $caseSensitive = true) : ? object
{ {
return $this->searchFieldAnnotationList($field, $annotationType, $caseSensitive)[0] ?? null; return $this->searchFieldAnnotationList($field, $annotationType, $caseSensitive)[0] ?? null;

View File

@ -56,7 +56,10 @@ class DatasetHandler
} }
elseif ( $field->expectType('array') ) { elseif ( $field->expectType('array') ) {
if ( is_string($value)) { if ( is_string($value)) {
if (substr($value, 0, 1) === "a") { if (empty($value)) {
yield $field->name => [];
}
elseif (substr($value, 0, 1) === "a") {
yield $field->name => unserialize($value); yield $field->name => unserialize($value);
} }

View File

@ -13,9 +13,6 @@ class Column
{ {
use \Ulmus\EntityTrait; use \Ulmus\EntityTrait;
#[Field\Id]
public ? id $srs_id;
#[Field(name: "TABLE_CATALOG", length: 512)] #[Field(name: "TABLE_CATALOG", length: 512)]
public string $tableCatalog; public string $tableCatalog;
@ -64,24 +61,10 @@ class Column
# #[Field(name: "COLLATION_TYPE", type: "longtext")] # #[Field(name: "COLLATION_TYPE", type: "longtext")]
# public string $type; # public string $type;
#[Field(name: "COLUMN_KEY", length: 3)]
public string $key;
#[Field(name: "EXTRA", length: 30)]
public string $extra;
#[Field(name: "PRIVILEGES", length: 80)]
public string $privileges;
#[Field(name: "COLUMN_COMMENT", length: 1024)]
public string $comment;
# #[Field(name: "IS_GENERATED", length: 6)] # #[Field(name: "IS_GENERATED", length: 6)]
# public string $generated; # public string $generated;
#[Field(name: "GENERATION_EXPRESSION", type: "longtext")]
public ? string $generationExpression;
public function matchFieldDefinition(ReflectedProperty $definition) : bool public function matchFieldDefinition(ReflectedProperty $definition) : bool
{ {
$nullable = $this->nullable === 'YES'; $nullable = $this->nullable === 'YES';

View File

@ -2,8 +2,28 @@
namespace Ulmus\Entity\Mysql; namespace Ulmus\Entity\Mysql;
use Ulmus\Attribute\Property\Field;
class Column extends \Ulmus\Entity\InformationSchema\Column class Column extends \Ulmus\Entity\InformationSchema\Column
{ {
#[Field\Id]
public ? id $srs_id;
#[Field(name: "COLUMN_KEY", length: 3)]
public string $key;
#[Field(name: "EXTRA", length: 30)]
public string $extra;
#[Field(name: "PRIVILEGES", length: 80)]
public string $privileges;
#[Field(name: "COLUMN_COMMENT", length: 1024)]
public string $comment;
#[Field(name: "GENERATION_EXPRESSION", type: "longtext")]
public ? string $generationExpression;
# TODO ! Handle FUNCTIONAL default value # TODO ! Handle FUNCTIONAL default value
protected function canHaveDefaultValue(): bool protected function canHaveDefaultValue(): bool
{ {
@ -12,4 +32,5 @@ class Column extends \Ulmus\Entity\InformationSchema\Column
]); ]);
} }
} }

View File

@ -5,4 +5,4 @@ namespace Ulmus\Migration\Sql;
class SqliteMigration class SqliteMigration
{ {
} }

View File

@ -0,0 +1,57 @@
<?php
namespace Ulmus\Migration;
use Ulmus\Adapter\AdapterInterface;
use Ulmus\Adapter\SqlFieldMapper;
use Ulmus\ConnectionAdapter;
use Ulmus\Entity\Mysql\Table;
use Ulmus\Repository;
trait SqlMigrationTrait
{
protected array $QUERY_REPLACE = [
'change' => "alter",
];
public function schemaTable(ConnectionAdapter $adapter, $databaseName, string $tableName) : null|object
{
return \Ulmus\Entity\InformationSchema\Table::repository(Repository::DEFAULT_ALIAS, $adapter)
->select(\Ulmus\Common\Sql::raw('this.*'))
->where($this->escapeIdentifier('table_schema', AdapterInterface::IDENTIFIER_FIELD), $databaseName)
->or($this->escapeIdentifier('table_catalog', AdapterInterface::IDENTIFIER_FIELD), $databaseName)
->loadOneFromField($this->escapeIdentifier('table_name', AdapterInterface::IDENTIFIER_FIELD), $tableName);
}
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
{
$mapper = new SqlFieldMapper($field);
return $typeOnly ? $mapper->type : $mapper->render();
}
public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable
{
if ($field['action'] === 'add') {
if (! empty($field['previous'])) {
$position = sprintf('AFTER %s', $this->escapeIdentifier($field['previous']->field, AdapterInterface::IDENTIFIER_FIELD));
}
else {
$position = "FIRST";
}
}
return implode(" ", array_filter([
strtoupper($field['action']),
$this->escapeIdentifier($definition->getSqlName(), AdapterInterface::IDENTIFIER_FIELD),
$definition->getSqlType(),
$definition->getSqlParams(),
$position ?? null,
]));
}
public function splitAlterQuery() : bool
{
return false;
}
}

View File

@ -12,6 +12,8 @@ class Select extends Fragment {
public bool $union = false; public bool $union = false;
public bool $isInternalSelect = false;
public ? int $top = null; public ? int $top = null;
protected array $fields = []; protected array $fields = [];

View File

@ -416,7 +416,8 @@ class Repository implements RepositoryInterface
public function withJoin(string|array $fields, array $options = []) : self public function withJoin(string|array $fields, array $options = []) : self
{ {
$canSelect = null === $this->queryBuilder->getFragment(Query\Select::class); $selectObj = $this->queryBuilder->getFragment(Query\Select::class);
$canSelect = empty($selectObj) || $selectObj->isInternalSelect === true;
if ( $canSelect ) { if ( $canSelect ) {
$select = $this->entityResolver->fieldList(EntityResolver::KEY_COLUMN_NAME, true); $select = $this->entityResolver->fieldList(EntityResolver::KEY_COLUMN_NAME, true);
@ -424,18 +425,13 @@ class Repository implements RepositoryInterface
} }
# @TODO Apply FILTER annotation to this too ! # @TODO Apply FILTER annotation to this too !
foreach(array_filter((array) $fields) as $item) { foreach(array_filter((array) $fields, fn($e) => ! isset($this->joined[$e])) as $item) {
if ( isset($this->joined[$item]) ) { $this->joined[$item] = true;
continue;
}
else {
$this->joined[$item] = true;
}
$attribute = $this->entityResolver->searchFieldAnnotation($item, [ Join::class ]) ?: $attribute = $this->entityResolver->searchFieldAnnotation($item, [ Join::class ]) ?:
$this->entityResolver->searchFieldAnnotation($item, [ Relation::class ]); $this->entityResolver->searchFieldAnnotation($item, [ Relation::class ]);
$isRelation = ( $attribute instanceof Relation ) || ($attribute instanceof Relation); $isRelation = $attribute instanceof Relation;
if ($isRelation && ( $attribute->isManyToMany() )) { if ($isRelation && ( $attribute->isManyToMany() )) {
throw new \Exception("Many-to-many relation can not be preloaded within joins."); throw new \Exception("Many-to-many relation can not be preloaded within joins.");
@ -444,7 +440,7 @@ class Repository implements RepositoryInterface
if ( $attribute ) { if ( $attribute ) {
$alias = $attribute->alias ?? $item; $alias = $attribute->alias ?? $item;
$entity = $attribute->entity ?? $this->entityResolver->reflectedClass->getProperties(true)[$item]->getTypes()[0]->type; $entity = $attribute->entity ?? $this->entityResolver->getPropertyEntityType($item);
foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if ( null === $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Relation\Ignore::class ]) ) { if ( null === $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Relation\Ignore::class ]) ) {
@ -462,12 +458,12 @@ class Repository implements RepositoryInterface
$this->open(); $this->open();
if ( ! in_array(WithOptionEnum::SkipWhere, $options)) { if ( ! in_array(WithOptionEnum::SkipWhere, $options)) {
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ] ) as $condition) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ] ) as $condition) {
if ( is_object($condition->field) && ( $condition->field->entityClass !== $entity ) ) { if ( is_object($condition->field) && ( $condition->field->entityClass !== $entity ) ) {
$this->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator); $this->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
}
} }
} }
}
if ( ! in_array(WithOptionEnum::SkipHaving, $options)) { if ( ! in_array(WithOptionEnum::SkipHaving, $options)) {
foreach ($this->entityResolver->searchFieldAnnotationList($item, [ Having::class ]) as $condition) { foreach ($this->entityResolver->searchFieldAnnotationList($item, [ Having::class ]) as $condition) {
@ -485,9 +481,9 @@ class Repository implements RepositoryInterface
$key = is_string($attribute->key) ? $this->entityClass::field($attribute->key) : $attribute->key; $key = is_string($attribute->key) ? $this->entityClass::field($attribute->key) : $attribute->key;
$foreignKey = is_string($attribute->foreignKey) ? $entity::field($attribute->foreignKey, $alias) : $attribute->foreignKey; $foreignKey = $this->evalClosure($attribute->foreignKey, $entity, $alias);
$this->join("LEFT", $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias, function($join) use ($item, $entity, $alias, $options) { $this->join($isRelation ? "LEFT" : $attribute->type, $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias, function($join) use ($item, $entity, $alias, $options) {
if ( ! in_array(WithOptionEnum::SkipJoinWhere, $options)) { if ( ! in_array(WithOptionEnum::SkipJoinWhere, $options)) {
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ]) as $condition) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ]) as $condition) {
if ( ! is_object($condition->field) ) { if ( ! is_object($condition->field) ) {
@ -518,6 +514,12 @@ class Repository implements RepositoryInterface
} }
} }
if ($canSelect) {
if ( $selectObj ??= $this->queryBuilder->getFragment(Query\Select::class) ) {
$selectObj->isInternalSelect = true;
}
}
return $this; return $this;
} }
@ -549,7 +551,7 @@ class Repository implements RepositoryInterface
->selectJsonEntity($relationRelation->entity, $alias)->open(); ->selectJsonEntity($relationRelation->entity, $alias)->open();
} }
else { else {
$entity = $relation->entity ?? $this->entityResolver->properties[$item]['type']; $entity = $relation->entity ?? $this->entityResolver->getPropertyEntityType($name);
$repository = $entity::repository()->selectJsonEntity($entity, $alias)->open(); $repository = $entity::repository()->selectJsonEntity($entity, $alias)->open();
} }
@ -590,7 +592,7 @@ class Repository implements RepositoryInterface
$order = $this->entityResolver->searchFieldAnnotationList($name, [ OrderBy::class ]); $order = $this->entityResolver->searchFieldAnnotationList($name, [ OrderBy::class ]);
$where = $this->entityResolver->searchFieldAnnotationList($name, [ Where::class ]); $where = $this->entityResolver->searchFieldAnnotationList($name, [ Where::class ]);
$baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->properties[$name]['type']; $baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->getPropertyEntityType($name);
$baseEntityResolver = $baseEntity::resolveEntity(); $baseEntityResolver = $baseEntity::resolveEntity();
$property = ($baseEntityResolver->field($relation->foreignKey, 01, false) ?: $baseEntityResolver->field($relation->foreignKey, 02))['name']; $property = ($baseEntityResolver->field($relation->foreignKey, 01, false) ?: $baseEntityResolver->field($relation->foreignKey, 02))['name'];
@ -644,6 +646,22 @@ class Repository implements RepositoryInterface
} }
} }
## AWAITING THE Closures in constant expression from PHP 8.5 !
protected function evalClosure(string $content, string $entityClass, mixed $alias = self::DEFAULT_ALIAS) : mixed
{
if (is_string($content)) {
if ( str_starts_with($content, 'fn(') ) {
$closure = eval("return $content;");
return $closure($this);
}
return $entityClass::field($content, $alias);
}
return $content;
}
public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest, bool $count = true) : self public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest, bool $count = true) : self
{ {
if ($count) { if ($count) {
@ -748,7 +766,15 @@ class Repository implements RepositoryInterface
public function instanciateEntity(? string $entityClass = null) : object public function instanciateEntity(? string $entityClass = null) : object
{ {
$entity = ( new \ReflectionClass($entityClass ?? $this->entityClass) )->newInstanceWithoutConstructor(); $entityClass ??= $this->entityClass;
try {
$entity = new $entityClass();
}
catch (\Throwable $ex) {
$entity = ( new \ReflectionClass($entityClass) )->newInstanceWithoutConstructor();
}
$entity->initializeEntity(); $entity->initializeEntity();
return $entity; return $entity;

View File

@ -3,7 +3,7 @@
namespace Ulmus\Repository; namespace Ulmus\Repository;
use Ulmus\{ use Ulmus\{
Ulmus, Query, Common\EntityResolver, Repository, Event, Ulmus, Query, Common\EntityResolver, Repository, Event, Common\Sql
}; };
use Ulmus\Attribute\Property\{ use Ulmus\Attribute\Property\{
@ -57,7 +57,7 @@ class RelationBuilder
} }
else { else {
if ( $relation = $this->resolver->searchFieldAnnotation($name, [ Relation::class ] ) ) { if ( $relation = $this->resolver->searchFieldAnnotation($name, [ Relation::class, Join::class ] ) ) {
return $this->instanciateEmptyObject($name, $relation); return $this->instanciateEmptyObject($name, $relation);
} }
elseif ($virtual = $this->resolveVirtual($name)) { elseif ($virtual = $this->resolveVirtual($name)) {
@ -276,11 +276,16 @@ class RelationBuilder
if ( $relation->generateKey ) { if ( $relation->generateKey ) {
$value = call_user_func_array($relation->generateKey, [ $this->entity ]); $value = call_user_func_array($relation->generateKey, [ $this->entity ]);
} }
else { elseif (isset($this->entity->$field)) {
$value = $this->entity->$field; $value = $this->entity->$field;
} }
$this->repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey), $value ); if (isset($value)) {
$this->repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey), $value );
}
else {
$this->repository->where(Sql::raw('TRUE'), Sql::raw('FALSE'));
}
} }
return $this->applyFilter($this->repository, $name); return $this->applyFilter($this->repository, $name);
@ -340,7 +345,7 @@ class RelationBuilder
return []; return [];
} }
protected function fieldIsNullable(string $name) : bool protected function fieldIsNullable(string $name) : bool
{ {
return $this->resolver->reflectedClass->getProperties(true)[$name]->allowsNull(); return $this->resolver->reflectedClass->getProperties(true)[$name]->allowsNull();

View File

@ -5,7 +5,7 @@ namespace Ulmus\Repository;
enum WithOptionEnum enum WithOptionEnum
{ {
case SkipWhere; case SkipWhere;
case SkipHaving; case SkipHaving;
case SkipFilter; case SkipFilter;
case SkipOrderBy; case SkipOrderBy;
case SkipJoinWhere; case SkipJoinWhere;