Skip to content

Commit

Permalink
Merge 7e18b2e into 9e257c8
Browse files Browse the repository at this point in the history
  • Loading branch information
hrach committed Jan 12, 2020
2 parents 9e257c8 + 7e18b2e commit e11e16a
Show file tree
Hide file tree
Showing 29 changed files with 161 additions and 97 deletions.
6 changes: 3 additions & 3 deletions doc/collection.texy
Expand Up @@ -39,14 +39,14 @@ $books = $orm->books->findBy([

Allowed operators are `=`, `!=`, `<=`, `<`, `>=` and `>`.

You can filter the collection using conditions using entity relationships. To filter collection by a relationship, use a *traversing expression*: it consists of the path delimited by `->` - an arrow. You have to start the expression by referencing the starting point using `this` keyword.
You can filter the collection using conditions using entity relationships. To filter collection by a relationship, use a *traversing expression*: it consists of the path delimited by `->` - the same arrow you use in PHP.

/--php
// find all books which were authored by Jon Snow
$orm->books->findBy(['this->author->name' => 'Jon Snow']);
$orm->books->findBy(['author->name' => 'Jon Snow']);

// find all books which were not translated by Jon Snow
$orm->books->findBy(['this->translator->name!=' => 'Jon Snow']);
$orm->books->findBy(['translator->name!=' => 'Jon Snow']);
\--

The described syntax may be expanded to support a `OR` logical conjunction. Unshift the operator `ICollection::OR` as a first value of the query array:
Expand Down
2 changes: 1 addition & 1 deletion doc/embeddables.texy
Expand Up @@ -42,7 +42,7 @@ class Address extends Nextras\Orm\Entity\Embeddable
The example by default store data in `address_street` column, etc. You may also filter by the nested structure. This works for both array and dbal collection.

/--php
$users = $orm->users->findBy(['this->address->city' => 'Prague']);
$users = $orm->users->findBy(['address->city' => 'Prague']);
\--

Setting value require creating new embeddable instance by yourself. If you want change its property, create a new one and set it again.
Expand Down
4 changes: 2 additions & 2 deletions doc/entity-sti.texy
Expand Up @@ -51,12 +51,12 @@ class AddressesRepository extends Nextras\Orm\Repository\Repository
Usage
=====

Collection calls will by default return a mixed result - with both types. You may filter these collection by the common properties defined on the abstract class. If you want to filter by property that is not shared between all entities, it's your responsibility to filter the proper entities fist. To access not shared properties, prepend a class name into the expression path.
Collection calls will by default return a mixed result - with both types. You may filter these collection by the common properties defined on the abstract class. If you want to filter by property that is not shared between all entities, it's your responsibility to filter the proper entities fist. To access non-shared properties, prepend a class name with double colon into the expression path.

/--php
$orm->addresses->findBy([
'type' => Address::TYPE_PUBLIC,
'Address->maintainer->id' => $maintainerId,
'Address::maintainer->id' => $maintainerId,
]);
\--

Expand Down
2 changes: 1 addition & 1 deletion doc/repository.texy
Expand Up @@ -37,7 +37,7 @@ final class BooksRepository extends Repository
*/
public function findByTags($name)
{
return $this->findBy(['this->tags->name' => $name]);
return $this->findBy(['tags->name' => $name]);
}
}
\--
Expand Down
33 changes: 24 additions & 9 deletions src/Collection/Helpers/ConditionParserHelper.php
Expand Up @@ -14,7 +14,6 @@

class ConditionParserHelper
{
/** @const operators */
public const OPERATOR_EQUAL = '=';
public const OPERATOR_NOT_EQUAL = '!=';
public const OPERATOR_GREATER = '>';
Expand All @@ -38,20 +37,36 @@ public static function parsePropertyOperator(string $condition): array
*/
public static function parsePropertyExpr(string $propertyPath): array
{
if (!\preg_match('#^([\w\\\]+(?:->\w++)*+)\z#', $propertyPath, $matches)) {
if (!\preg_match('#
^
(?:([\w\\\]+)::)?
([\w\\\]++(?:->\w++)*+)
$
#x', $propertyPath, $matches)) {
throw new InvalidArgumentException('Unsupported condition format.');
}

$source = null;
$tokens = \explode('->', $matches[1]);
if (\count($tokens) > 1) {
$source = \array_shift($tokens);
$source = $source === 'this' ? null : $source;
if ($source !== null && !\is_subclass_of($source, IEntity::class)) {
throw new InvalidArgumentException("Property expression '$propertyPath' uses unknown class '$source'.");
\array_shift($matches); // whole expression

/** @var string $source */
$source = \array_shift($matches);
$tokens = \explode('->', \array_shift($matches));

if ($source === '') {
$source = null;
if ($tokens[0] === 'this') {
\trigger_error("Using 'this->' is deprecated; use property traversing directly without 'this->'.", E_USER_DEPRECATED);
\array_shift($tokens);
} elseif (\strpos($tokens[0], '\\') !== false) {
$source = \array_shift($tokens);
\trigger_error("Using STI class prefix '$source->' is deprecated; use with double-colon '$source::'.", E_USER_DEPRECATED);
}
}

if ($source !== null && !\is_subclass_of($source, IEntity::class)) {
throw new InvalidArgumentException("Property expression '$propertyPath' uses class '$source' that is not " . IEntity::class . '.');
}

return [$tokens, $source];
}
}
2 changes: 1 addition & 1 deletion src/Mapper/Memory/RelationshipMapperOneHasMany.php
Expand Up @@ -44,7 +44,7 @@ public function getIterator(IEntity $parent, ICollection $collection): Iterator
{
assert($this->metadata->relationship !== null);
$className = $this->metadata->relationship->entityMetadata->className;
$data = $collection->findBy(["$className->{$this->joinStorageKey}->id" => $parent->getValue('id')])->fetchAll();
$data = $collection->findBy(["$className::{$this->joinStorageKey}->id" => $parent->getValue('id')])->fetchAll();
return new EntityIterator($data);
}

Expand Down
13 changes: 10 additions & 3 deletions src/Repository/RemovalHelper.php
Expand Up @@ -35,7 +35,7 @@ public static function getCascadeQueueAndSetNulls(IEntity $entity, IModel $model

list ($pre, $post, $nulls) = static::getRelationships($entity);
$prePersist = [];
static::setNulls($entity, $nulls, $model, $prePersist);
static::setNulls($entity, $nulls, $model, $prePersist, $queueRemove);

if (!$withCascade) {
$queueRemove[$entityHash] = $entity;
Expand All @@ -45,6 +45,7 @@ public static function getCascadeQueueAndSetNulls(IEntity $entity, IModel $model
foreach ($prePersist as $value) {
$queuePersist[spl_object_hash($value)] = $value;
}
$queueRemove[$entityHash] = true;
foreach ($pre as $value) {
if ($value instanceof IEntity) {
static::getCascadeQueueAndSetNulls($value, $model, true, $queuePersist, $queueRemove);
Expand All @@ -55,6 +56,7 @@ public static function getCascadeQueueAndSetNulls(IEntity $entity, IModel $model
$queuePersist[spl_object_hash($value)] = $value;
}
}
unset($queueRemove[$entityHash]);
$queueRemove[$entityHash] = $entity;
unset($queuePersist[$entityHash]);
foreach ($post as $value) {
Expand Down Expand Up @@ -115,7 +117,7 @@ public static function getRelationships(IEntity $entity): array
/**
* @param PropertyMetadata[] $metadata
*/
private static function setNulls(IEntity $entity, array $metadata, IModel $model, array & $pre)
private static function setNulls(IEntity $entity, array $metadata, IModel $model, array & $pre, array & $queueRemove)
{
foreach ($metadata as $propertyMeta) {
assert($propertyMeta->relationship !== null);
Expand Down Expand Up @@ -147,7 +149,12 @@ private static function setNulls(IEntity $entity, array $metadata, IModel $model
$property = $entity->getProperty($name);
assert($property instanceof HasOne);
if ($reverseProperty !== null && $entity->hasValue($name)) {
$pre[] = $entity->getValue($name)->getProperty($reverseProperty->name);
$reverseEntity = $entity->getValue($name);
if (isset($queueRemove[spl_object_hash($reverseEntity)])) {
// reverse side is also being removed, do not set null to this relationship
continue;
}
$pre[] = $reverseEntity->getProperty($reverseProperty->name);
}
$property->set(null, true);

Expand Down
Expand Up @@ -20,15 +20,15 @@ class CollectionEmbeddablesTest extends DataTestCase
{
public function testBasics()
{
$books1 = $this->orm->books->findBy(['this->price->cents>=' => 100]);
$books1 = $this->orm->books->findBy(['price->cents>=' => 100]);
Assert::same(0, $books1->count());
Assert::same(0, $books1->countStored());

$book = $this->orm->books->getById(1);
$book->price = new Money(100, Currency::CZK());
$this->orm->persistAndFlush($book);

$books2 = $this->orm->books->findBy(['this->price->cents>=' => 100]);
$books2 = $this->orm->books->findBy(['price->cents>=' => 100]);
Assert::same(1, $books2->count());
Assert::same(1, $books2->countStored());
}
Expand Down
34 changes: 17 additions & 17 deletions tests/cases/integration/Collection/collection.phpt
Expand Up @@ -66,10 +66,10 @@ class CollectionTest extends DataTestCase

public function testCountOnLimitedWithJoin()
{
$collection = $this->orm->books->findBy(['this->author->name' => 'Writer 1'])->orderBy('id')->limitBy(5);
$collection = $this->orm->books->findBy(['author->name' => 'Writer 1'])->orderBy('id')->limitBy(5);
Assert::same(2, $collection->countStored());

$collection = $this->orm->tagFollowers->findBy(['this->tag->name' => 'Tag 1'])->orderBy('tag')->limitBy(3);
$collection = $this->orm->tagFollowers->findBy(['tag->name' => 'Tag 1'])->orderBy('tag')->limitBy(3);
Assert::same(1, $collection->countStored());
}

Expand All @@ -91,13 +91,13 @@ class CollectionTest extends DataTestCase
public function testOrdering()
{
$ids = $this->orm->books->findAll()
->orderBy('this->author->id', ICollection::DESC)
->orderBy('author->id', ICollection::DESC)
->orderBy('title', ICollection::ASC)
->fetchPairs(null, 'id');
Assert::same([3, 4, 1, 2], $ids);

$ids = $this->orm->books->findAll()
->orderBy('this->author->id', ICollection::DESC)
->orderBy('author->id', ICollection::DESC)
->orderBy('title', ICollection::DESC)
->fetchPairs(null, 'id');
Assert::same([4, 3, 2, 1], $ids);
Expand All @@ -108,15 +108,15 @@ class CollectionTest extends DataTestCase
{
$ids = $this->orm->books->findAll()
->orderByMultiple([
'this->author->id' => ICollection::DESC,
'author->id' => ICollection::DESC,
'title' => ICollection::ASC,
])
->fetchPairs(null, 'id');
Assert::same([3, 4, 1, 2], $ids);

$ids = $this->orm->books->findAll()
->orderByMultiple([
'this->author->id' => ICollection::DESC,
'author->id' => ICollection::DESC,
'title' => ICollection::DESC,
])
->fetchPairs(null, 'id');
Expand All @@ -127,25 +127,25 @@ class CollectionTest extends DataTestCase
public function testOrderingWithOptionalProperty()
{
$bookIds = $this->orm->books->findAll()
->orderBy('this->translator->name', ICollection::ASC_NULLS_FIRST)
->orderBy('translator->name', ICollection::ASC_NULLS_FIRST)
->orderBy('id')
->fetchPairs(null, 'id');
Assert::same([2, 1, 3, 4], $bookIds);

$bookIds = $this->orm->books->findAll()
->orderBy('this->translator->name', ICollection::DESC_NULLS_FIRST)
->orderBy('translator->name', ICollection::DESC_NULLS_FIRST)
->orderBy('id')
->fetchPairs(null, 'id');
Assert::same([2, 3, 4, 1], $bookIds);

$bookIds = $this->orm->books->findAll()
->orderBy('this->translator->name', ICollection::ASC_NULLS_LAST)
->orderBy('translator->name', ICollection::ASC_NULLS_LAST)
->orderBy('id')
->fetchPairs(null, 'id');
Assert::same([1, 3, 4, 2], $bookIds);

$bookIds = $this->orm->books->findAll()
->orderBy('this->translator->name', ICollection::DESC_NULLS_LAST)
->orderBy('translator->name', ICollection::DESC_NULLS_LAST)
->orderBy('id')
->fetchPairs(null, 'id');
Assert::same([3, 4, 1, 2], $bookIds);
Expand Down Expand Up @@ -179,8 +179,8 @@ class CollectionTest extends DataTestCase
public function testConditionsInSameJoin()
{
$books = $this->orm->books->findBy([
'this->author->name' => 'Writer 1',
'this->author->web' => 'http://example.com/1',
'author->name' => 'Writer 1',
'author->web' => 'http://example.com/1',
]);

Assert::same(2, $books->count());
Expand All @@ -199,8 +199,8 @@ class CollectionTest extends DataTestCase
$this->orm->books->persistAndFlush($book);

$books = $this->orm->books->findBy([
'this->author->name' => 'Writer 1',
'this->translator->web' => 'http://example.com/2',
'author->name' => 'Writer 1',
'translator->web' => 'http://example.com/2',
]);

Assert::same(1, $books->count());
Expand Down Expand Up @@ -229,8 +229,8 @@ class CollectionTest extends DataTestCase
$book4 = $this->orm->books->getById(4);

$books = $this->orm->books->findBy([
'this->nextPart->ean->code' => '123',
'this->previousPart->ean->code' => '456',
'nextPart->ean->code' => '123',
'previousPart->ean->code' => '456',
]);

Assert::count(1, $books);
Expand Down Expand Up @@ -308,7 +308,7 @@ class CollectionTest extends DataTestCase

public function testDistinct()
{
$books = $this->orm->tagFollowers->findBy(['this->tag->books->id' => 1]);
$books = $this->orm->tagFollowers->findBy(['tag->books->id' => 1]);
Assert::count(2, $books);
}
}
Expand Down
Expand Up @@ -222,7 +222,7 @@ class RelationshipManyHasManyTest extends DataTestCase

public function testCountStoredOnManyToManyCondition()
{
$books = $this->orm->books->findBy(['this->tags->name' => 'Tag 2']);
$books = $this->orm->books->findBy(['tags->name' => 'Tag 2']);
Assert::same(2, $books->countStored());
}
}
Expand Down
Expand Up @@ -137,7 +137,7 @@ class RelationshipOneHasManyTest extends DataTestCase
public function testOrderingWithJoins()
{
$book = $this->orm->books->getById(1);
$books = $book->translator->books->get()->orderBy('this->ean->code')->fetchAll();
$books = $book->translator->books->get()->orderBy('ean->code')->fetchAll();
Assert::count(2, $books);
}

Expand Down
Expand Up @@ -32,8 +32,8 @@ class RelationshipOneHasOneTest extends DataTestCase
$this->orm->books->persistAndFlush($book);

$eans = $this->orm->eans
->findBy(['this->book->title' => 'GoT'])
->orderBy('this->book->title');
->findBy(['book->title' => 'GoT'])
->orderBy('book->title');
Assert::equal(1, $eans->countStored());
Assert::equal(1, $eans->count());
Assert::equal('GoTEAN', $eans->fetch()->code);
Expand Down Expand Up @@ -151,11 +151,11 @@ class RelationshipOneHasOneTest extends DataTestCase

$this->orm->books->persistAndFlush($book);

$books = $this->orm->books->findBy(['this->ean->code' => '1234']);
$books = $this->orm->books->findBy(['ean->code' => '1234']);
Assert::same(1, $books->countStored());
Assert::same(1, $books->count());

$eans = $this->orm->eans->findBy(['this->book->title' => 'Games of Thrones I']);
$eans = $this->orm->eans->findBy(['book->title' => 'Games of Thrones I']);
Assert::same(1, $eans->countStored());
Assert::same(1, $eans->count());
}
Expand Down
17 changes: 14 additions & 3 deletions tests/cases/integration/Repository/repository.sti.phpt
Expand Up @@ -7,21 +7,21 @@

namespace NextrasTests\Orm\Integration\Repository;

use Mockery;
use Nextras\Dbal\Utils\DateTimeImmutable;
use NextrasTests\Orm\Comment;
use NextrasTests\Orm\DataTestCase;
use NextrasTests\Orm\Thread;
use Tester\Assert;


$dic = require_once __DIR__ . '/../../../bootstrap.php';


class RepositorySTITest extends DataTestCase
{

public function testSelect()
{
$thread = $this->orm->contents->findBy(['NextrasTests\Orm\Thread->id' => 1])->fetch();
$thread = $this->orm->contents->findBy(['id' => 1])->fetch();
Assert::type(Thread::class, $thread);
}

Expand All @@ -37,6 +37,17 @@ class RepositorySTITest extends DataTestCase
Assert::type(Comment::class, $comment);
}


public function testFindByFiltering()
{
$result = $this->orm->contents->findBy([
'type' => 'comment',
'NextrasTests\Orm\Comment::repliedAt>' => new DateTimeImmutable('2020-01-01 18:00:00'),
]);
Assert::same(1, $result->count());
Assert::same(1, $result->countStored());
Assert::type(Comment::class, $result->fetch());
}
}


Expand Down

0 comments on commit e11e16a

Please sign in to comment.