Merge branch 'fix'

This commit is contained in:
Yuri Kuznetsov
2025-10-26 11:28:24 +02:00
7 changed files with 177 additions and 37 deletions
@@ -61,6 +61,12 @@ class RelationConverter
RelationParam::INDEXES,
];
/** @var string[] */
private $manyMergeParams = [
RelationParam::ORDER_BY,
RelationParam::ORDER,
];
public function __construct(
private Metadata $metadata,
private InjectableFactory $injectableFactory,
@@ -112,7 +118,7 @@ class RelationConverter
$raw = $convertedEntityDefs->toAssoc();
if (isset($raw[EntityParam::RELATIONS][$name])) {
$this->mergeParams($raw[EntityParam::RELATIONS][$name], $params, $foreignParams ?? []);
$this->mergeParams($raw[EntityParam::RELATIONS][$name], $params, $foreignParams ?? [], $linkType);
$this->correct($raw[EntityParam::RELATIONS][$name]);
}
@@ -172,9 +178,18 @@ class RelationConverter
* @param array<string, mixed> $params
* @param array<string, mixed> $foreignParams
*/
private function mergeParams(array &$relationDefs, array $params, array $foreignParams): void
private function mergeParams(array &$relationDefs, array $params, array $foreignParams, string $linkType): void
{
foreach ($this->mergeParams as $name) {
$mergeParams = $this->mergeParams;
if (
$linkType === RelationType::HAS_MANY ||
$linkType === RelationType::HAS_CHILDREN
) {
$mergeParams = array_merge($mergeParams, $this->manyMergeParams);
}
foreach ($mergeParams as $name) {
$additionalParam = $this->getMergedParam($name, $params, $foreignParams);
if ($additionalParam === null) {
@@ -227,6 +242,13 @@ class RelationConverter
*/
private function correct(array &$relationDefs): void
{
if (
isset($relationDefs[RelationParam::ORDER]) &&
is_string($relationDefs[RelationParam::ORDER])
) {
$relationDefs[RelationParam::ORDER] = strtoupper($relationDefs[RelationParam::ORDER]);
}
if (!isset($relationDefs[RelationParam::ADDITIONAL_COLUMNS])) {
return;
}
@@ -93,4 +93,18 @@ class RelationParam
* Deferred load.
*/
public const DEFERRED_LOAD = 'deferredLoad';
/**
* Default order by. Applied on the entity level.
*
* @since 9.2.5
*/
public const ORDER_BY = 'orderBy';
/**
* Default order. Applied on the entity level.
*
* @since 9.2.5
*/
public const ORDER = 'order';
}
+23 -15
View File
@@ -32,12 +32,14 @@ namespace Espo\ORM\Relation;
use Espo\ORM\BaseEntity;
use Espo\ORM\Defs\Defs;
use Espo\ORM\Defs\Params\RelationParam;
use Espo\ORM\Defs\RelationDefs;
use Espo\ORM\Entity;
use Espo\ORM\EntityCollection;
use Espo\ORM\EntityManager;
use Espo\ORM\Mapper\RDBMapper;
use Espo\ORM\Name\Attribute;
use Espo\ORM\Query\Part\Order;
use Espo\ORM\Repository\RDBRelationSelectBuilder;
use Espo\ORM\Type\RelationType;
use LogicException;
use RuntimeException;
@@ -293,22 +295,11 @@ class RDBRelations implements Relations
->getEntity($this->getEntity()->getEntityType())
->getRelation($relation);
$orderBy = null;
$order = null;
$builder = $this->entityManager
->getRelation($this->getEntity(), $relation)
->createBuilder();
if ($relationDefs->getParam('orderBy')) {
$orderBy = $relationDefs->getParam('orderBy');
if ($relationDefs->getParam('order')) {
$order = strtoupper($relationDefs->getParam('order')) === Order::DESC ? Order::DESC : Order::ASC;
}
}
$builder = $this->entityManager->getRelation($this->getEntity(), $relation);
if ($orderBy) {
$builder->order($orderBy, $order);
}
$this->applyOrder($relationDefs, $builder);
$collection = $builder->find();
@@ -398,4 +389,21 @@ class RDBRelations implements Relations
return $foreignEntity;
}
private function applyOrder(RelationDefs $relationDefs, RDBRelationSelectBuilder $builder): void
{
$orderBy = $relationDefs->getParam(RelationParam::ORDER_BY);
if (!$orderBy) {
return;
}
$order = $relationDefs->getParam(RelationParam::ORDER);
if ($order !== null) {
$order = strtoupper($order) === Order::DESC ? Order::DESC : Order::ASC;
}
$builder->order($orderBy, $order);
}
}
@@ -51,7 +51,7 @@ use RuntimeException;
/**
* An access point for a specific relation of a record.
*
* @template TEntity of Entity
* @template TEntity of Entity = Entity
*/
class RDBRelation
{
@@ -95,6 +95,18 @@ class RDBRelation
}
}
/**
* Create a select builder.
*
* @return Builder<TEntity>
*
* @since 9.2.5
*/
public function createBuilder(): Builder
{
return $this->createSelectBuilder();
}
/**
* Create a select builder.
*
@@ -53,7 +53,7 @@ use InvalidArgumentException;
/**
* Builds select parameters for related records for RDB repository.
*
* @template TEntity of Entity
* @template TEntity of Entity = Entity
*/
class RDBRelationSelectBuilder
{
@@ -234,7 +234,7 @@ class RDBRepository implements Repository
*/
public function find(): EntityCollection|SthCollection
{
return $this->createSelectBuilder()->find();
return $this->createBuilder()->find();
}
/**
@@ -273,7 +273,7 @@ class RDBRepository implements Repository
*/
public function count(): int
{
return $this->createSelectBuilder()->count();
return $this->createBuilder()->count();
}
/**
@@ -283,7 +283,7 @@ class RDBRepository implements Repository
*/
public function max(string $attribute)
{
return $this->createSelectBuilder()->max($attribute);
return $this->createBuilder()->max($attribute);
}
/**
@@ -293,7 +293,7 @@ class RDBRepository implements Repository
*/
public function min(string $attribute)
{
return $this->createSelectBuilder()->min($attribute);
return $this->createBuilder()->min($attribute);
}
/**
@@ -303,7 +303,7 @@ class RDBRepository implements Repository
*/
public function sum(string $attribute)
{
return $this->createSelectBuilder()->sum($attribute);
return $this->createBuilder()->sum($attribute);
}
/**
@@ -334,7 +334,7 @@ class RDBRepository implements Repository
*/
public function join($target, ?string $alias = null, $conditions = null): RDBSelectBuilder
{
return $this->createSelectBuilder()->join($target, $alias, $conditions);
return $this->createBuilder()->join($target, $alias, $conditions);
}
/**
@@ -348,7 +348,7 @@ class RDBRepository implements Repository
*/
public function leftJoin($target, ?string $alias = null, $conditions = null): RDBSelectBuilder
{
return $this->createSelectBuilder()->leftJoin($target, $alias, $conditions);
return $this->createBuilder()->leftJoin($target, $alias, $conditions);
}
/**
@@ -358,7 +358,7 @@ class RDBRepository implements Repository
*/
public function distinct(): RDBSelectBuilder
{
return $this->createSelectBuilder()->distinct();
return $this->createBuilder()->distinct();
}
/**
@@ -368,7 +368,7 @@ class RDBRepository implements Repository
*/
public function forUpdate(): RDBSelectBuilder
{
return $this->createSelectBuilder()->forUpdate();
return $this->createBuilder()->forUpdate();
}
/**
@@ -378,7 +378,7 @@ class RDBRepository implements Repository
*/
public function sth(): RDBSelectBuilder
{
return $this->createSelectBuilder()->sth();
return $this->createBuilder()->sth();
}
/**
@@ -395,7 +395,7 @@ class RDBRepository implements Repository
*/
public function where($clause = [], $value = null): RDBSelectBuilder
{
return $this->createSelectBuilder()->where($clause, $value);
return $this->createBuilder()->where($clause, $value);
}
/**
@@ -412,7 +412,7 @@ class RDBRepository implements Repository
*/
public function having($clause = [], $value = null): RDBSelectBuilder
{
return $this->createSelectBuilder()->having($clause, $value);
return $this->createBuilder()->having($clause, $value);
}
/**
@@ -432,7 +432,7 @@ class RDBRepository implements Repository
*/
public function order($orderBy = Attribute::ID, $direction = null): RDBSelectBuilder
{
return $this->createSelectBuilder()->order($orderBy, $direction);
return $this->createBuilder()->order($orderBy, $direction);
}
/**
@@ -442,7 +442,7 @@ class RDBRepository implements Repository
*/
public function limit(?int $offset = null, ?int $limit = null): RDBSelectBuilder
{
return $this->createSelectBuilder()->limit($offset, $limit);
return $this->createBuilder()->limit($offset, $limit);
}
/**
@@ -462,7 +462,7 @@ class RDBRepository implements Repository
*/
public function select($select = [], ?string $alias = null): RDBSelectBuilder
{
return $this->createSelectBuilder()->select($select, $alias);
return $this->createBuilder()->select($select, $alias);
}
/**
@@ -479,15 +479,17 @@ class RDBRepository implements Repository
*/
public function group($groupBy): RDBSelectBuilder
{
return $this->createSelectBuilder()->group($groupBy);
return $this->createBuilder()->group($groupBy);
}
/**
* Create a select builder.
*
* @return RDBSelectBuilder<TEntity>
*
* @since 9.2.5
*/
protected function createSelectBuilder(): RDBSelectBuilder
public function createBuilder(): RDBSelectBuilder
{
/** @var RDBSelectBuilder<TEntity> $builder */
$builder = new RDBSelectBuilder($this->entityManager, $this->entityType);
@@ -495,6 +497,19 @@ class RDBRepository implements Repository
return $builder;
}
/**
* Create a select builder.
*
* @return RDBSelectBuilder<TEntity>
*
* @deprecated Since v9.2.5. Use `createBuilder` instead.
* @todo Remove in v10.0.
*/
protected function createSelectBuilder(): RDBSelectBuilder
{
return $this->createBuilder();
}
/**
* Use hooks instead.
*
@@ -29,8 +29,10 @@
namespace tests\integration\Espo\ORM;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Modules\Crm\Entities\Lead;
use Espo\Modules\Crm\Entities\Task;
use Espo\ORM\Defs\Params\RelationParam;
use Espo\ORM\EntityCollection;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Opportunity;
@@ -232,4 +234,71 @@ class RelationsTest extends BaseTestCase
$this->assertEquals(null, $lead2->get('createdAccountId'));
}
public function testOrder(): void
{
$em = $this->getEntityManager();
$account1 = $em->createEntity(Account::ENTITY_TYPE, ['name' => 'a1']);
$account2 = $em->createEntity(Account::ENTITY_TYPE, ['name' => 'a2']);
$contact = $em->createEntity(Contact::ENTITY_TYPE);
$this->assertInstanceOf(Contact::class, $contact);
$em->getRelation($contact, 'accounts')->relate($account1);
$em->getRelation($contact, 'accounts')->relate($account2);
//
$metadata = $this->getMetadata();
$metadata->set('entityDefs', Contact::ENTITY_TYPE, [
'links' => [
'accounts' => [
RelationParam::ORDER_BY => 'name',
RelationParam::ORDER => 'desc',
]
]
]);
$metadata->save();
$this->reCreateApplication();
$em = $this->getEntityManager();
$contact = $em->getRDBRepositoryByClass(Contact::class)->getById($contact->getId());
$this->assertNotNull($contact);
$accounts = $contact->getAccounts();
$this->assertCount(2, $accounts);
$this->assertEquals('a2', $accounts[0]->getName());
$this->assertEquals('a1', $accounts[1]->getName());
//
$metadata = $this->getMetadata();
$metadata->set('entityDefs', Contact::ENTITY_TYPE, [
'links' => [
'accounts' => [
RelationParam::ORDER_BY => 'name',
RelationParam::ORDER => 'asc',
]
]
]);
$metadata->save();
$this->reCreateApplication();
$em = $this->getEntityManager();
$contact = $em->getRDBRepositoryByClass(Contact::class)->getById($contact->getId());
$this->assertNotNull($contact);
$accounts = $contact->getAccounts();
$this->assertCount(2, $accounts);
$this->assertEquals('a1', $accounts[0]->getName());
$this->assertEquals('a2', $accounts[1]->getName());
}
}