Skip to content

Commit

Permalink
Merge 3.x (#1294)
Browse files Browse the repository at this point in the history
* create CountFilter (#1280)
* 3.28 (#1284)
* DevKit updates (#1286)
* Close API (#1292)
* fix CountFilter
* fix CountFilter after merge
* fix CountFilterTest after merge

Co-authored-by: rgrassian <[email protected]>
Co-authored-by: Vincent Langlet <[email protected]>
Co-authored-by: Sonata CI <[email protected]>
  • Loading branch information
4 people authored Feb 3, 2021
1 parent 35aad95 commit e440690
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 16 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [3.28.0](https://github.com/sonata-project/SonataDoctrineORMAdminBundle/compare/3.27.0...3.28.0) - 2021-01-26
### Added
- [[#1280](https://github.com/sonata-project/SonataDoctrineORMAdminBundle/pull/1280)] Added `CountFilter`. ([@rgrassian](https://github.com/rgrassian))

### Changed
- [[#1268](https://github.com/sonata-project/SonataDoctrineORMAdminBundle/pull/1268)] Use Doctrine ORM Paginator to count in Pager. ([@VincentLanglet](https://github.com/VincentLanglet))

### Deprecated
- [[#1268](https://github.com/sonata-project/SonataDoctrineORMAdminBundle/pull/1268)] `Pager::CONCAT_SEPARATOR` ([@VincentLanglet](https://github.com/VincentLanglet))

### Fixed
- [[#1265](https://github.com/sonata-project/SonataDoctrineORMAdminBundle/pull/1265)] Do not provide a default `null` `field_type` option for Filter ([@VincentLanglet](https://github.com/VincentLanglet))
- [[#1268](https://github.com/sonata-project/SonataDoctrineORMAdminBundle/pull/1268)] Support of composite key for computeNbResult ([@VincentLanglet](https://github.com/VincentLanglet))

## [3.27.0](https://github.com/sonata-project/SonataDoctrineORMAdminBundle/compare/3.26.0...3.27.0) - 2021-01-17
### Added
- [[#1262](https://github.com/sonata-project/SonataDoctrineORMAdminBundle/pull/1262)] Added Pager::getCurrentPageResults() ([@VincentLanglet](https://github.com/VincentLanglet))
Expand Down
17 changes: 1 addition & 16 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ If your PR contains an addition, a new feature, this one has to be fully covered

Some rules have to be respected about the test:

* Prefer [the built-in test doubles implementation](https://phpunit.de/manual/current/en/test-doubles.html) over prophecy.
* Annotations about coverage are prohibited. This concerns:
* `@covers`
* `@coversDefaultClass`
Expand All @@ -161,22 +162,6 @@ Some rules have to be respected about the test:
* Most of the time, the test class SHOULD have the same name as the targeted class, suffixed by `Test`.
* The `@expectedException*` annotations are prohibited. Use `PHPUnit_Framework_TestCase::setExpectedException()`.

##### Using test doubles

Historically, Sonata has been using [the built-in test doubles implementation](https://phpunit.de/manual/current/en/test-doubles.html),
but [started to use Prophecy](https://github.com/sonata-project/dev-kit/issues/89).
This means the current Sonata codebase currently uses both implementations.

If you want to contribute a test that uses test doubles, please follow these rules :

1. All new test classes MUST use built-in test double implementation.
2. If you are changing an existing test method, you MUST use the same implementation it already uses,
and focus on the goal of your PR and only on that.
3. If you are changing an existing test class, you MUST use the same implementation it already uses,
to be more consistent.
4. You MAY submit a PR that migrates a test class from Prophecy to the built-in test double implementation,
but it MUST migrate it entirely. The PR SHOULD only be about the migration.

### Writing a Pull Request

#### Subject
Expand Down
1 change: 1 addition & 0 deletions docs/reference/filter_field_definition.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ For now, only `Doctrine ORM` filters are available:
* ``Sonata\DoctrineORMAdminBundle\Filter\BooleanFilter``: depends on the ``Sonata\AdminBundle\Form\Type\Filter\DefaultType`` Form Type, renders yes or no field,
* ``Sonata\DoctrineORMAdminBundle\Filter\CallbackFilter``: depends on the ``Sonata\AdminBundle\Form\Type\Filter\DefaultType`` Form Type, types can be configured as needed,
* ``Sonata\DoctrineORMAdminBundle\Filter\ChoiceFilter``: depends on the ``Sonata\AdminBundle\Form\Type\Filter\ChoiceType`` Form Type,
* ``Sonata\DoctrineORMAdminBundle\Filter\CountFilter``: depends on the ``Sonata\AdminBundle\Form\Type\Filter\NumberType`` Form Type,
* ``Sonata\DoctrineORMAdminBundle\Filter\NumberFilter``: depends on the ``Sonata\AdminBundle\Form\Type\Filter\NumberType`` Form Type,
* ``Sonata\DoctrineORMAdminBundle\Filter\ModelAutocompleteFilter``: uses ``Sonata\AdminBundle\Form\Type\Filter\ModelAutocompleteType`` form type, can be used as replacement of ``Sonata\DoctrineORMAdminBundle\Filter\ModelFilter`` to handle too many items that cannot be loaded into memory.
* ``Sonata\DoctrineORMAdminBundle\Filter\StringFilter``: depends on the ``Sonata\AdminBundle\Form\Type\Filter\ChoiceType`` Form Type,
Expand Down
2 changes: 2 additions & 0 deletions src/Datagrid/ProxyQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
/**
* This class try to unify the query usage with Doctrine.
*
* @final since sonata-project/doctrine-orm-admin-bundle 3.x
*
* @method Query\Expr expr()
* @method QueryBuilder setCacheable($cacheable)
* @method bool isCacheable()
Expand Down
3 changes: 3 additions & 0 deletions src/Exporter/DataSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
use Sonata\Exporter\Source\DoctrineORMQuerySourceIterator;
use Sonata\Exporter\Source\SourceIteratorInterface;

/**
* @final since sonata-project/doctrine-orm-admin-bundle 3.x
*/
class DataSource implements DataSourceInterface
{
public function createIterator(ProxyQueryInterface $query, array $fields): SourceIteratorInterface
Expand Down
68 changes: 68 additions & 0 deletions src/Filter/CountFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Sonata\DoctrineORMAdminBundle\Filter;

use Sonata\AdminBundle\Form\Type\Filter\NumberType;
use Sonata\AdminBundle\Form\Type\Operator\NumberOperatorType;
use Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQueryInterface;
use Symfony\Component\Form\Extension\Core\Type\NumberType as FormNumberType;

final class CountFilter extends Filter
{
public const CHOICES = [
NumberOperatorType::TYPE_EQUAL => '=',
NumberOperatorType::TYPE_GREATER_EQUAL => '>=',
NumberOperatorType::TYPE_GREATER_THAN => '>',
NumberOperatorType::TYPE_LESS_EQUAL => '<=',
NumberOperatorType::TYPE_LESS_THAN => '<',
];

public function filter(ProxyQueryInterface $query, string $alias, string $field, array $data): void
{
if (!\array_key_exists('value', $data) || !is_numeric($data['value'])) {
return;
}

$type = $data['type'] ?? NumberOperatorType::TYPE_EQUAL;
$operator = $this->getOperator((int) $type);

// c.name > '1' => c.name OPERATOR :FIELDNAME
$parameterName = $this->getNewParameterName($query);
$rootAlias = current($query->getQueryBuilder()->getRootAliases());
$query->getQueryBuilder()->addGroupBy($rootAlias);
$this->applyWhere($query, sprintf('COUNT(%s.%s) %s :%s', $alias, $field, $operator, $parameterName));
$query->getQueryBuilder()->setParameter($parameterName, $data['value']);
}

public function getDefaultOptions(): array
{
return [
'field_type' => FormNumberType::class,
];
}

public function getRenderSettings(): array
{
return [NumberType::class, [
'field_type' => $this->getFieldType(),
'field_options' => $this->getFieldOptions(),
'label' => $this->getLabel(),
]];
}

private function getOperator(int $type): string
{
return self::CHOICES[$type] ?? self::CHOICES[NumberOperatorType::TYPE_EQUAL];
}
}
15 changes: 15 additions & 0 deletions src/Filter/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@ protected function applyWhere(ProxyQueryInterface $query, $parameter): void
$this->active = true;
}

/**
* @param mixed $parameter
*/
protected function applyHaving(ProxyQueryInterface $query, $parameter): void
{
if (self::CONDITION_OR === $this->getCondition()) {
$query->getQueryBuilder()->orHaving($parameter);
} else {
$query->getQueryBuilder()->andHaving($parameter);
}

// filter is active since it's added to the queryBuilder
$this->active = true;
}

protected function getNewParameterName(ProxyQueryInterface $query): string
{
// dots are not accepted in a DQL identifier so replace them
Expand Down
75 changes: 75 additions & 0 deletions tests/Filter/CountFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Sonata\DoctrineORMAdminBundle\Tests\Filter;

use Sonata\AdminBundle\Form\Type\Operator\NumberOperatorType;
use Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery;
use Sonata\DoctrineORMAdminBundle\Filter\CountFilter;

class CountFilterTest extends FilterTestCase
{
public function testFilterEmpty(): void
{
$filter = new CountFilter();
$filter->initialize('field_name', ['field_options' => ['class' => 'FooBar']]);

$builder = new ProxyQuery($this->createQueryBuilderStub());

$filter->filter($builder, 'alias', 'field', []);

$this->assertSame([], $builder->query);
$this->assertFalse($filter->isActive());
}

public function testFilterInvalidOperator(): void
{
$filter = new CountFilter();
$filter->initialize('field_name', ['field_options' => ['class' => 'FooBar']]);

$builder = new ProxyQuery($this->createQueryBuilderStub());

$filter->filter($builder, 'alias', 'field', ['type' => 'foo']);

$this->assertSame([], $builder->query);
$this->assertFalse($filter->isActive());
}

/**
* @dataProvider filterDataProvider
*/
public function testFilter(string $expected, ?int $type): void
{
$filter = new CountFilter();
$filter->initialize('field_name', ['field_options' => ['class' => 'FooBar']]);

$builder = new ProxyQuery($this->createQueryBuilderStub());

$filter->filter($builder, 'alias', 'field', ['type' => $type, 'value' => 42]);

$this->assertSame(['GROUP BY o', $expected], $builder->query);
$this->assertTrue($filter->isActive());
}

public function filterDataProvider(): array
{
return [
['COUNT(alias.field) = :field_name_0', NumberOperatorType::TYPE_EQUAL],
['COUNT(alias.field) >= :field_name_0', NumberOperatorType::TYPE_GREATER_EQUAL],
['COUNT(alias.field) > :field_name_0', NumberOperatorType::TYPE_GREATER_THAN],
['COUNT(alias.field) <= :field_name_0', NumberOperatorType::TYPE_LESS_EQUAL],
['COUNT(alias.field) < :field_name_0', NumberOperatorType::TYPE_LESS_THAN],
['COUNT(alias.field) = :field_name_0', null],
];
}
}
12 changes: 12 additions & 0 deletions tests/Filter/FilterTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ static function ($query) use ($queryBuilder): void {
}
);

$queryBuilder->method('andHaving')->willReturnCallback(
static function ($query) use ($queryBuilder): void {
$queryBuilder->query[] = (string) $query;
}
);

$queryBuilder->method('addGroupBy')->willReturnCallback(
static function (string $groupBy) use ($queryBuilder): void {
$queryBuilder->query[] = sprintf('GROUP BY %s', $groupBy);
}
);

$queryBuilder->method('expr')->willReturnCallback(
static function () use ($testCase): Expr {
return $testCase->createExprStub();
Expand Down

0 comments on commit e440690

Please sign in to comment.