Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ v3.0.0
* Change: Minimum version of doctrine/dbal is now 4.0
* Add: `Bdf\Prime\Connection\Middleware\DebugStack\DebugStackMiddleware` in replacement of doctrine's `DebugStack` for query logging and debugging
* Change: `Bdf\Prime\Prime::configure()` now can take the `middlewares` option to defined middlewares on all connections.
* Change: `Bdf\Prime\Query\QueryRepositoryExtension::by()` now add the requested columns to the select clause if not already present.
* Add: `Bdf\Prime\Query\Contract\Projectionable::addProjection()` to add columns to the select clause, only if not already present.
* Change: Records now supports `by()` method on query to index the result by the given column.
* Add: `Bdf\Prime\Collection\Indexer\RecordIndexer` implementation of `EntityIndexerInterface` for indexing records, using a closure to extract the properties which are not publicly accessible.
* Add: `transformer` parameter on `Bdf\Prime\Record\Field` and `Bdf\Prime\Record\LoadRelation` to apply a transformation on the value when hydrating the record.
* Add: `as` parameter on `Bdf\Prime\Record\LoadRelation` to load the relation as a record instead of the entity.
* Change: `Bdf\Prime\Record\LoadRelation::$relation` is now optional : if not provided, the type of the parameter will be used as relation name.
* Change: on `Bdf\Prime\Record\LoadRelation`, if `as` and `transformer` are not provided, and the parameter type differs from the relation entity class, the parameter type will be used as read record.
* Add: `Bdf\Prime\Relations\RelationInterface::loadRecordByForeignKeys()` to load relation entities as records, without owner entity.
* Fix: `morphMany` relation : the discriminator was not applied when loading a single foreign key, because of the `KeyValueQuery` optimisation.
* Add: `Bdf\Prime\Record\Embedded` to handle embedded values on records

BC Breaks:
* Remove: `Bdf\Prime\Visitor` package, with mapper and graphviz visitors without replacements
Expand All @@ -26,6 +37,9 @@ BC Breaks:
* `Bdf\Prime\Platform\PlatformInterface::apply()`
* Change: use `LockMode` enum instead of int constant on `Bdf\Prime\Query\Contract\Lockable` methods
* Change: `Bdf\Prime\Connection\ShardingConnection::getDatabase()` now return the database name of the default connection instead of empty string
* Add: `Bdf\Prime\Query\Contract\Projectionable::addProjection()` method
* Add: `Bdf\Prime\Relations\RelationInterface::loadRecordByForeignKeys()` method to load records instead of entities
* Change: `Bdf\Prime\Record\RecordHydratorInterface::finalize()` now takes raw database rows as last parameter

Removal of deprecated APIs:
* Remove: `Bdf\Prime\Cache\DoctrineCacheAdapter`. Use `Bdf\Prime\Cache\SimpleCacheAdapter` or `Bdf\Prime\Cache\CachePoolAdapter` instead.
Expand Down
113 changes: 113 additions & 0 deletions src/Collection/Indexer/RecordIndexer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

namespace Bdf\Prime\Collection\Indexer;

use Closure;

/**
* Base implementation of EntityIndexer
*
* @template E as object
* @implements EntityIndexerInterface<E>
*/
final class RecordIndexer implements EntityIndexerInterface
{
/**
* All indexed entities
*
* @var E[]
*/
private array $entities = [];

/**
* Map of indexes
* Indexes are indexed by the key name, and store entities in mode "group by combine"
*
* @var E[][][]
*/
private array $indexed = [];


/**
* @param list<string> $indexes List of initial indexes keys to use. Entities will be indexed with theses keys when pushed
*/
public function __construct(
/**
* Extract a property from the record, if the property is not publicly available.
* Takes as first parameter the record instance, and as second the property name.
*
* @var Closure(E, string):mixed
*/
private readonly Closure $extractor,
array $indexes = []
) {
$this->indexed = array_fill_keys($indexes, []);
}

/**
* Push the entity to the indexer
* Active indexes will be updated
*
* @param E $entity Entity to add
*
* @return void
*/
public function push($entity): void
{
$this->entities[] = $entity;

foreach ($this->indexed as $key => &$indexed) {
$property = $entity->$key ?? ($this->extractor)($entity, $key);
$indexed[$property][] = $entity;
}
}

/**
* {@inheritdoc}
*/
public function by(string $key): array
{
if (isset($this->indexed[$key])) {
return $this->indexed[$key];
}

$indexed = [];

foreach ($this->entities as $entity) {
$property = $entity->$key ?? ($this->extractor)($entity, $key);
$indexed[$property][] = $entity;
}

return $this->indexed[$key] = $indexed;
}

/**
* {@inheritdoc}
*/
public function byOverride(string $key): array
{
$result = [];

foreach ($this->by($key) as $key => $value) {
$result[$key] = end($value);
}

return $result;
}

/**
* {@inheritdoc}
*/
public function all(): array
{
return $this->entities;
}

/**
* {@inheritdoc}
*/
public function empty(): bool
{
return empty($this->entities);
}
}
2 changes: 1 addition & 1 deletion src/Query/AbstractReadCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ public function postProcessResult(ResultSetInterface $data): iterable
$hydrated[] = $recordManager->instantiate($recordClassName, $row, $platform);
}

$hydrated = $recordManager->finalize($recordClassName, $hydrated);
$hydrated = $recordManager->finalize($recordClassName, $hydrated, $proceed);
} else {
$hydrated = $proceed;
}
Expand Down
24 changes: 24 additions & 0 deletions src/Query/Contract/Projectionable.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,28 @@ interface Projectionable
*/
public function project($columns = null);

/**
* Adds an item that is to be returned in the query result, if not yet present.
* To define an alias, an associative array must be used, with the alias as key, and expression as value.
*
* Note: To ensure that expressions string will not be parsed, use expression objects, or wrap with `new Raw('...')`
*
* <code>
* $query
* ->project('u.id')
* ->addProjection('p.id')
* ->from('users', 'u');
* </code>
*
* @param ColumnType|ColumnType[] $columns The selection expression.
*
* @return $this This Query instance.
*
* @see Projectionable::select() for exemples
* @see Projectionable::addSelect() Same as this method, but do not check if the projection is already present.
*/
public function addProjection($columns);

/**
* Specifies an item that is to be returned in the query result.
* Replaces any previously specified selections, if any.
Expand Down Expand Up @@ -72,7 +94,9 @@ public function select($columns = null);
* @param ColumnType|ColumnType[]|null $columns The selection expression.
*
* @return $this This Query instance.
*
* @see Projectionable::select() for exemples
* @see Projectionable::addProjection() Same as this method, but check if the projection is already present.
*/
public function addSelect($columns);
}
32 changes: 32 additions & 0 deletions src/Query/Extension/ProjectionableTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
use Bdf\Prime\Query\Compiler\CompilerState;
use Bdf\Prime\Query\Contract\Projectionable;

use function func_get_args;
use function is_array;
use function is_int;

/**
* Trait for @see Projectionable
*
Expand All @@ -23,6 +27,34 @@ public function project($columns = null)
return $this->select($columns);
}

/**
* @see Projectionable::addProjection()
*/
public function addProjection($columns)
{
// Empty project means that all fields are projected, so no need to add new projections
if ($this->statements['columns'] === []) {
return $this;
}

$this->compilerState->invalidate('columns');

$columns = is_array($columns) ? $columns : [$columns];

foreach ($columns as $alias => $column) {
$toAdd = [
'column' => $column,
'alias' => is_int($alias) ? null : $alias,
];

if (!in_array($toAdd, $this->statements['columns'])) {
$this->statements['columns'][] = $toAdd;
}
}

return $this;
}

/**
* @see Projectionable::select()
*/
Expand Down
Loading
Loading