Reimplemented initial grids for API on top of new ResourceController TDD style

This commit is contained in:
Paweł Jędrzejewski 2016-01-10 00:51:47 +01:00
parent bd842bb295
commit 934f615e81
90 changed files with 4839 additions and 40 deletions

View file

@ -8,6 +8,7 @@ suites:
Contact: { namespace: Sylius\Component\Contact, psr4_prefix: Sylius\Component\Contact, spec_path: src/Sylius/Component/Contact, src_path: src/Sylius/Component/Contact }
Core: { namespace: Sylius\Component\Core, psr4_prefix: Sylius\Component\Core, spec_path: src/Sylius/Component/Core, src_path: src/Sylius/Component/Core }
Currency: { namespace: Sylius\Component\Currency, psr4_prefix: Sylius\Component\Currency, spec_path: src/Sylius/Component/Currency, src_path: src/Sylius/Component/Currency }
Grid: { namespace: Sylius\Component\Grid, psr4_prefix: Sylius\Component\Grid, spec_path: src/Sylius/Component/Grid, src_path: src/Sylius/Component/Grid }
Inventory: { namespace: Sylius\Component\Inventory, psr4_prefix: Sylius\Component\Inventory, spec_path: src/Sylius/Component/Inventory, src_path: src/Sylius/Component/Inventory }
Locale: { namespace: Sylius\Component\Locale, psr4_prefix: Sylius\Component\Locale, spec_path: src/Sylius/Component/Locale, src_path: src/Sylius/Component/Locale }
Mailer: { namespace: Sylius\Component\Mailer, psr4_prefix: Sylius\Component\Mailer, spec_path: src/Sylius/Component/Mailer, src_path: src/Sylius/Component/Mailer }
@ -44,6 +45,7 @@ suites:
CoreBundle: { namespace: Sylius\Bundle\CoreBundle, psr4_prefix: Sylius\Bundle\CoreBundle, spec_path: src/Sylius/Bundle/CoreBundle, src_path: src/Sylius/Bundle/CoreBundle }
CurrencyBundle: { namespace: Sylius\Bundle\CurrencyBundle, psr4_prefix: Sylius\Bundle\CurrencyBundle, spec_path: src/Sylius/Bundle/CurrencyBundle, src_path: src/Sylius/Bundle/CurrencyBundle }
FlowBundle: { namespace: Sylius\Bundle\FlowBundle, psr4_prefix: Sylius\Bundle\FlowBundle, spec_path: src/Sylius/Bundle/FlowBundle, src_path: src/Sylius/Bundle/FlowBundle }
GridBundle: { namespace: Sylius\Bundle\GridBundle, psr4_prefix: Sylius\Bundle\GridBundle, spec_path: src/Sylius/Bundle/GridBundle, src_path: src/Sylius/Bundle/GridBundle }
InstallerBundle: { namespace: Sylius\Bundle\InstallerBundle, psr4_prefix: Sylius\Bundle\InstallerBundle, spec_path: src/Sylius/Bundle/InstallerBundle, src_path: src/Sylius/Bundle/InstallerBundle }
InventoryBundle: { namespace: Sylius\Bundle\InventoryBundle, psr4_prefix: Sylius\Bundle\InventoryBundle, spec_path: src/Sylius/Bundle/InventoryBundle, src_path: src/Sylius/Bundle/InventoryBundle }
LocaleBundle: { namespace: Sylius\Bundle\LocaleBundle, psr4_prefix: Sylius\Bundle\LocaleBundle, spec_path: src/Sylius/Bundle/LocaleBundle, src_path: src/Sylius/Bundle/LocaleBundle }

View file

@ -58,8 +58,11 @@ sylius_api_tax_rate:
prefix: /tax-rates
sylius_api_shipping_category:
resource: @SyliusApiBundle/Resources/config/routing/shipping_category.yml
prefix: /shipping-categories
resource: |
alias: sylius.shipping_category
section: api
grid: sylius_admin_shipping_category
type: sylius.resource_api
sylius_api_shipping_method:
resource: @SyliusApiBundle/Resources/config/routing/shipping_method.yml

View file

@ -77,6 +77,7 @@ abstract class Kernel extends BaseKernel
new \Sylius\Bundle\CoreBundle\SyliusCoreBundle(),
new \Sylius\Bundle\WebBundle\SyliusWebBundle(),
new \Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
new \Sylius\Bundle\GridBundle\SyliusGridBundle(),
new \winzou\Bundle\StateMachineBundle\winzouStateMachineBundle(),
new \Sylius\Bundle\ApiBundle\SyliusApiBundle(),
@ -127,6 +128,18 @@ abstract class Kernel extends BaseKernel
return $bundles;
}
/**
* {@inheritdoc}
*/
protected function getContainerBaseClass()
{
if ('test' === $this->environment) {
return '\PSS\SymfonyMockerContainer\DependencyInjection\MockerContainer';
}
return parent::getContainerBaseClass();
}
/**
* {@inheritdoc}
*/

View file

@ -822,3 +822,18 @@ sylius_review:
reviewer:
classes:
model: %sylius.model.customer.class%
sylius_grid:
grids:
sylius_admin_shipping_category:
driver:
name: doctrine/orm
options:
class: %sylius.model.shipping_category.class%
sorting:
name: desc
filters:
name:
type: string
code:
type: string

View file

@ -83,6 +83,45 @@ EOT;
$this->assertResponse($response, 'shipping_category/index_response', Response::HTTP_OK);
}
public function testGetShippingCategoriesSortedListResponse()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$this->loadFixturesFromFile('resources/shipping_categories.yml');
$this->client->request('GET', '/api/shipping-categories/', ['sorting' => ['name' => 'asc']], [], [
'HTTP_Authorization' => 'Bearer SampleTokenNjZkNjY2MDEwMTAzMDkxMGE0OTlhYzU3NzYyMTE0ZGQ3ODcyMDAwM2EwMDZjNDI5NDlhMDdlMQ',
]);
$response = $this->client->getResponse();
$this->assertResponse($response, 'shipping_category/sorted_index_response', Response::HTTP_OK);
}
public function testGetShippingCategoriesFilteredListByNameResponse()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$this->loadFixturesFromFile('resources/shipping_categories_for_filtering.yml');
$this->client->request('GET', '/api/shipping-categories/', ['criteria' => ['name' => 'heavy']], [], [
'HTTP_Authorization' => 'Bearer SampleTokenNjZkNjY2MDEwMTAzMDkxMGE0OTlhYzU3NzYyMTE0ZGQ3ODcyMDAwM2EwMDZjNDI5NDlhMDdlMQ',
]);
$response = $this->client->getResponse();
$this->assertResponse($response, 'shipping_category/filtered_by_name_index_response', Response::HTTP_OK);
}
public function testGetShippingCategoriesFilteredListByCodeResponse()
{
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$this->loadFixturesFromFile('resources/shipping_categories_for_filtering.yml');
$this->client->request('GET', '/api/shipping-categories/', ['criteria' => ['code' => 'SC1']], [], [
'HTTP_Authorization' => 'Bearer SampleTokenNjZkNjY2MDEwMTAzMDkxMGE0OTlhYzU3NzYyMTE0ZGQ3ODcyMDAwM2EwMDZjNDI5NDlhMDdlMQ',
]);
$response = $this->client->getResponse();
$this->assertResponse($response, 'shipping_category/filtered_by_code_index_response', Response::HTTP_OK);
}
public function testGetShippingCategoryAccessDeniedResponse()
{
$this->client->request('GET', '/api/shipping-categories/1');

View file

@ -0,0 +1,13 @@
Sylius\Component\Shipping\Model\ShippingCategory:
shipping_category_{1..3}:
code: SC<current()>
name: Shipping Category <current()>
description: <sentence($nbWords = 6)>
shipping_category_4:
code: SC4
name: Heavy Items
description: <sentence($nbWords = 6)>
shipping_category_5:
code: SC5
name: Very Heavy Items
description: <sentence($nbWords = 6)>

View file

@ -0,0 +1,31 @@
{
"page": 1,
"limit": 10,
"pages": 1,
"total": 1,
"_links": {
"self": {
"href": "\/api\/shipping-categories\/?criteria%5Bcode%5D=SC1&page=1&limit=10"
},
"first": {
"href": "\/api\/shipping-categories\/?criteria%5Bcode%5D=SC1&page=1&limit=10"
},
"last": {
"href": "\/api\/shipping-categories\/?criteria%5Bcode%5D=SC1&page=1&limit=10"
}
},
"_embedded": {
"items": [
{
"id": @integer@,
"code": "SC1",
"name": "Shipping Category 1",
"_links": {
"self": {
"href": "@string@"
}
}
}
]
}
}

View file

@ -0,0 +1,41 @@
{
"page": 1,
"limit": 10,
"pages": 1,
"total": 2,
"_links": {
"self": {
"href": "\/api\/shipping-categories\/?criteria%5Bname%5D=heavy&page=1&limit=10"
},
"first": {
"href": "\/api\/shipping-categories\/?criteria%5Bname%5D=heavy&page=1&limit=10"
},
"last": {
"href": "\/api\/shipping-categories\/?criteria%5Bname%5D=heavy&page=1&limit=10"
}
},
"_embedded": {
"items": [
{
"id": @integer@,
"code": "SC5",
"name": "Very Heavy Items",
"_links": {
"self": {
"href": "@string@"
}
}
},
{
"id": @integer@,
"code": "SC4",
"name": "Heavy Items",
"_links": {
"self": {
"href": "@string@"
}
}
}
]
}
}

View file

@ -4,5 +4,10 @@
"name": "Shipping Category 1",
"description": "@string@",
"created_at": "@string@.isDateTime()",
"updated_at": "@string@.isDateTime()"
"updated_at": "@string@.isDateTime()",
"_links": {
"self": {
"href": "@string@"
}
}
}

View file

@ -0,0 +1,51 @@
{
"page": 1,
"limit": 10,
"pages": 1,
"total": 3,
"_links": {
"self": {
"href": "\/api\/shipping-categories\/?sorting%5Bname%5D=asc&page=1&limit=10"
},
"first": {
"href": "\/api\/shipping-categories\/?sorting%5Bname%5D=asc&page=1&limit=10"
},
"last": {
"href": "\/api\/shipping-categories\/?sorting%5Bname%5D=asc&page=1&limit=10"
}
},
"_embedded": {
"items": [
{
"id": @integer@,
"code": "SC1",
"name": "Shipping Category 1",
"_links": {
"self": {
"href": "@string@"
}
}
},
{
"id": @integer@,
"code": "SC2",
"name": "Shipping Category 2",
"_links": {
"self": {
"href": "@string@"
}
}
},
{
"id": @integer@,
"code": "SC3",
"name": "Shipping Category 3",
"_links": {
"self": {
"href": "@string@"
}
}
}
]
}
}

View file

@ -4,5 +4,10 @@
"name": "Light",
"description": "Light weight items",
"created_at": "@string@.isDateTime()",
"updated_at": "@string@.isDateTime()"
"updated_at": "@string@.isDateTime()",
"_links": {
"self": {
"href": "@string@"
}
}
}

View file

@ -0,0 +1,5 @@
vendor/
bin/
composer.phar
composer.lock

View file

@ -0,0 +1,10 @@
language: php
php:
- 5.5
- 5.6
- 7.0
before_script: composer install --no-interaction --prefer-source
script: bin/phpspec run -fpretty --verbose

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\GridBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class RegisterDriversPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$registry = $container->getDefinition('sylius.registry.grid_driver');
foreach ($container->findTaggedServiceIds('sylius.grid_driver') as $id => $attributes) {
if (!isset($attributes[0]['alias'])) {
throw new \InvalidArgumentException('Tagged grid drivers needs to have `alias` attribute.');
}
$registry->addMethodCall('register', array($attributes[0]['alias'], new Reference($id)));
}
}
}

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\GridBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class RegisterFiltersPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$registry = $container->getDefinition('sylius.registry.grid_filter');
foreach ($container->findTaggedServiceIds('sylius.grid_filter') as $id => $attributes) {
if (!isset($attributes[0]['type'])) {
throw new \InvalidArgumentException('Tagged grid filters needs to have `type` attribute.');
}
$registry->addMethodCall('register', array($attributes[0]['type'], new Reference($id)));
}
}
}

View file

@ -0,0 +1,105 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\GridBundle\DependencyInjection;
use Sylius\Bundle\ResourceBundle\SyliusResourceBundle;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class Configuration implements ConfigurationInterface
{
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('sylius_grid');
$this->addGridsSection($rootNode);
return $treeBuilder;
}
/**
* Adds `grids` section.
*
* @param ArrayNodeDefinition $node
*/
private function addGridsSection(ArrayNodeDefinition $node)
{
$node
->children()
->arrayNode('grids')
->useAttributeAsKey('code')
->prototype('array')
->children()
->arrayNode('driver')
->children()
->scalarNode('name')->defaultValue('doctrine/orm')->end()
->arrayNode('options')
->prototype('variable')->end()
->defaultValue(array())
->end()
->end()
->end()
->arrayNode('sorting')
->prototype('scalar')->end()
->end()
->arrayNode('fields')
->useAttributeAsKey('name')
->prototype('array')
->children()
->scalarNode('type')->isRequired()->cannotBeEmpty()->end()
->scalarNode('path')->cannotBeEmpty()->end()
->scalarNode('label')->cannotBeEmpty()->end()
->arrayNode('options')
->prototype('variable')->end()
->end()
->end()
->end()
->end()
->arrayNode('filters')
->useAttributeAsKey('name')
->prototype('array')
->children()
->scalarNode('type')->isRequired()->cannotBeEmpty()->end()
->scalarNode('label')->cannotBeEmpty()->end()
->arrayNode('options')
->prototype('variable')->end()
->end()
->end()
->end()
->end()
->arrayNode('actions')
->useAttributeAsKey('name')
->prototype('array')
->children()
->scalarNode('type')->isRequired()->end()
->scalarNode('label')->end()
->arrayNode('options')
->prototype('variable')->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
;
}
}

View file

@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\GridBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class SyliusGridExtension extends Extension
{
/**
* {@inheritdoc}
*/
public function load(array $config, ContainerBuilder $container)
{
$processor = new Processor();
$config = $processor->processConfiguration(new Configuration(), $config);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.xml');
$loader->load('drivers.xml');
$loader->load('filters.xml');
$container->setParameter('sylius.grids_definitions', $config['grids']);
}
}

View file

@ -0,0 +1,84 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\GridBundle\Doctrine\DBAL;
use Doctrine\DBAL\Query\QueryBuilder;
use Pagerfanta\Adapter\DoctrineDbalAdapter;
use Pagerfanta\Pagerfanta;
use Sylius\Component\Grid\Data\DataSourceInterface;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class DataSource implements DataSourceInterface
{
/**
* @var QueryBuilder
*/
private $queryBuilder;
/**
* @var ExpressionBuilderInterface
*/
private $expressionBuilder;
/**
* @param QueryBuilder $queryBuilder
*/
function __construct(QueryBuilder $queryBuilder)
{
$this->queryBuilder = $queryBuilder;
$this->expressionBuilder = new ExpressionBuilder($queryBuilder);
}
/**
* {@inheritdoc}
*/
public function restrict($expression, $condition = DataSourceInterface::CONDITION_AND)
{
switch ($condition) {
case DataSourceInterface::CONDITION_AND:
$this->queryBuilder->andWhere($expression);
break;
case DataSourceInterface::CONDITION_OR:
$this->queryBuilder->orWhere($expression);
break;
}
}
/**
* {@inheritdoc}
*/
public function getExpressionBuilder()
{
return $this->expressionBuilder;
}
/**
* {@inheritdoc}
*/
public function getData(Parameters $parameters)
{
$countQueryBuilderModifier = function ($queryBuilder) {
$queryBuilder
->select('COUNT(DISTINCT o.id) AS total_results')
->setMaxResults(1)
;
};
$paginator = new Pagerfanta(new DoctrineDbalAdapter($this->queryBuilder, $countQueryBuilderModifier));
$paginator->setCurrentPage($parameters->get('page', 1));
return $paginator;
}
}

View file

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\GridBundle\Doctrine\DBAL;
use Doctrine\DBAL\Connection;
use Sylius\Component\Grid\Data\DriverInterface;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class Driver implements DriverInterface
{
/**
* @var Connection
*/
private $connection;
/**
* @param Connection $connection
*/
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
/**
* {@inheritdoc}
*/
public function getDataSource(array $configuration, Parameters $parameters)
{
if (!array_key_exists('table', $configuration)) {
throw new \InvalidArgumentException('"table" must be configured.');
}
$queryBuilder = $this->connection->createQueryBuilder();
$queryBuilder
->select('o.*')
->from($configuration['table'], 'o')
;
foreach ($configuration['aliases'] as $column => $alias) {
$queryBuilder->addSelect(sprintf('o.%s as %s', $column, $alias));
}
return new DataSource($queryBuilder);
}
}

View file

@ -0,0 +1,186 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\GridBundle\Doctrine\DBAL;
use Doctrine\DBAL\Query\QueryBuilder;
use Sylius\Component\Grid\Data\ExpressionBuilderInterface;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class ExpressionBuilder implements ExpressionBuilderInterface
{
/**
* @var QueryBuilder
*/
private $queryBuilder;
/**
* @param QueryBuilder $queryBuilder
*/
function __construct(QueryBuilder $queryBuilder)
{
$this->queryBuilder = $queryBuilder;
}
/**
* {@inheritdoc}
*/
public function andX($expressions)
{
$expr = $this->queryBuilder->expr();
if (is_array($expressions)) {
return call_user_func_array(array($expr, 'andX'), $expressions);
}
return $this->queryBuilder->expr()->andX(func_get_args());
}
/**
* {@inheritdoc}
*/
public function orX($expressions)
{
$expr = $this->queryBuilder->expr();
if (is_array($expressions)) {
return call_user_func_array(array($expr, 'orX'), $expressions);
}
return $this->queryBuilder->expr()->orX(func_get_args());
}
/**
* {@inheritdoc}
*/
public function comparison($field, $operator, $value)
{
// TODO: Implement comparison() method.
}
/**
* {@inheritdoc}
*/
public function equals($field, $value)
{
$this->queryBuilder->setParameter($field, $value);
return $this->queryBuilder->expr()->eq($field, ':'.$field);
}
/**
* {@inheritdoc}
*/
public function notEquals($field, $value)
{
$this->queryBuilder->setParameter($field, $value);
return $this->queryBuilder->expr()->neq($field, ':'.$field);
}
/**
* {@inheritdoc}
*/
public function lessThan($field, $value)
{
$this->queryBuilder->andWhere($field.' < :'.$field)->setParameter($field, $value);
}
/**
* {@inheritdoc}
*/
public function lessThanOrEqual($field, $value)
{
$this->queryBuilder->andWhere($field.' =< :'.$field)->setParameter($field, $value);
}
/**
* {@inheritdoc}
*/
public function greaterThan($field, $value)
{
$this->queryBuilder->andWhere($field.' > :'.$field)->setParameter($field, $value);
}
/**
* {@inheritdoc}
*/
public function greaterThanOrEqual($field, $value)
{
$this->queryBuilder->andWhere($field.' => :%s'.$field)->setParameter($field, $value);
}
/**
* {@inheritdoc}
*/
public function in($field, array $values)
{
return $this->queryBuilder->expr()->in($field, $values);
}
/**
* {@inheritdoc}
*/
public function notIn($field, array $values)
{
return $this->queryBuilder->expr()->notIn($field, $values);
}
/**
* {@inheritdoc}
*/
public function isNull($field)
{
return $this->queryBuilder->expr()->isNull($field);
}
/**
* {@inheritdoc}
*/
public function isNotNull($field)
{
return $this->queryBuilder->expr()->isNotNull($field);
}
/**
* {@inheritdoc}
*/
public function like($field, $pattern)
{
return $this->queryBuilder->expr()->like($field, $this->queryBuilder->expr()->literal($pattern));
}
/**
* {@inheritdoc}
*/
public function notLike($field, $pattern)
{
return $this->queryBuilder->expr()->notLike($field, $this->queryBuilder->expr()->literal($pattern));
}
/**
* {@inheritdoc}
*/
public function orderBy($field, $direction)
{
return $this->queryBuilder->orderBy($field, $direction);
}
/**
* {@inheritdoc}
*/
public function addOrderBy($field, $direction)
{
return $this->queryBuilder->addOrderBy($field, $direction);
}
}

View file

@ -0,0 +1,77 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\GridBundle\Doctrine\ORM;
use Doctrine\ORM\QueryBuilder;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use Sylius\Component\Grid\Data\DataSourceInterface;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class DataSource implements DataSourceInterface
{
/**
* @var QueryBuilder
*/
private $queryBuilder;
/**
* @var ExpressionBuilderInterface
*/
private $expressionBuilder;
/**
* @param QueryBuilder $queryBuilder
*/
function __construct(QueryBuilder $queryBuilder)
{
$this->queryBuilder = $queryBuilder;
$this->expressionBuilder = new ExpressionBuilder($queryBuilder);
}
/**
* {@inheritdoc}
*/
public function restrict($expression, $condition = DataSourceInterface::CONDITION_AND)
{
switch ($condition) {
case DataSourceInterface::CONDITION_AND:
$this->queryBuilder->andWhere($expression);
break;
case DataSourceInterface::CONDITION_OR:
$this->queryBuilder->orWhere($expression);
break;
}
}
/**
* {@inheritdoc}
*/
public function getExpressionBuilder()
{
return $this->expressionBuilder;
}
/**
* {@inheritdoc}
*/
public function getData(Parameters $parameters)
{
$paginator = new Pagerfanta(new DoctrineORMAdapter($this->queryBuilder));
$paginator->setCurrentPage($parameters->get('page', 1));
return $paginator;
}
}

View file

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\GridBundle\Doctrine\ORM;
use Doctrine\ORM\EntityManagerInterface;
use Sylius\Component\Grid\Data\DriverInterface;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class Driver implements DriverInterface
{
/**
* @var EntityManagerInterface
*/
private $entityManager;
/**
* @param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* {@inheritdoc}
*/
public function getDataSource(array $configuration, Parameters $parameters)
{
if (!array_key_exists('class', $configuration)) {
throw new \InvalidArgumentException('"class" must be configured.');
}
$repository = $this->entityManager->getRepository($configuration['class']);
$queryBuilder = $repository->createQueryBuilder('o');
return new DataSource($queryBuilder);
}
}

View file

@ -0,0 +1,198 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\GridBundle\Doctrine\ORM;
use Doctrine\ORM\QueryBuilder;
use Sylius\Component\Grid\Data\ExpressionBuilderInterface;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class ExpressionBuilder implements ExpressionBuilderInterface
{
/**
* @var QueryBuilder
*/
private $queryBuilder;
/**
* @param QueryBuilder $queryBuilder
*/
function __construct(QueryBuilder $queryBuilder)
{
$this->queryBuilder = $queryBuilder;
}
/**
* {@inheritdoc}
*/
public function andX($expressions)
{
$expr = $this->queryBuilder->expr();
if (is_array($expressions)) {
return call_user_func_array(array($expr, 'andX'), $expressions);
}
return $this->queryBuilder->expr()->andX(func_get_args());
}
/**
* {@inheritdoc}
*/
public function orX($expressions)
{
$expr = $this->queryBuilder->expr();
if (is_array($expressions)) {
return call_user_func_array(array($expr, 'orX'), $expressions);
}
return $this->queryBuilder->expr()->orX(func_get_args());
}
/**
* {@inheritdoc}
*/
public function comparison($field, $operator, $value)
{
// TODO: Implement comparison() method.
}
/**
* {@inheritdoc}
*/
public function equals($field, $value)
{
$this->queryBuilder->setParameter($field, $value);
return $this->queryBuilder->expr()->eq($this->getFieldName($field), ':'.$field);
}
/**
* {@inheritdoc}
*/
public function notEquals($field, $value)
{
$this->queryBuilder->setParameter($field, $value);
return $this->queryBuilder->expr()->neq($this->getFieldName($field), ':'.$field);
}
/**
* {@inheritdoc}
*/
public function lessThan($field, $value)
{
$this->queryBuilder->andWhere($this->getFieldName($field).' < :'.$field)->setParameter($field, $value);
}
/**
* {@inheritdoc}
*/
public function lessThanOrEqual($field, $value)
{
$this->queryBuilder->andWhere($this->getFieldName($field).' =< :'.$field)->setParameter($field, $value);
}
/**
* {@inheritdoc}
*/
public function greaterThan($field, $value)
{
$this->queryBuilder->andWhere($this->getFieldName($field).' > :'.$field)->setParameter($field, $value);
}
/**
* {@inheritdoc}
*/
public function greaterThanOrEqual($field, $value)
{
$this->queryBuilder->andWhere($this->getFieldName($field).' => :%s'.$field)->setParameter($field, $value);
}
/**
* {@inheritdoc}
*/
public function in($field, array $values)
{
return $this->queryBuilder->expr()->in($this->getFieldName($field), $values);
}
/**
* {@inheritdoc}
*/
public function notIn($field, array $values)
{
return $this->queryBuilder->expr()->notIn($this->getFieldName($field), $values);
}
/**
* {@inheritdoc}
*/
public function isNull($field)
{
return $this->queryBuilder->expr()->isNull($this->getFieldName($field));
}
/**
* {@inheritdoc}
*/
public function isNotNull($field)
{
return $this->queryBuilder->expr()->isNotNull($this->getFieldName($field));
}
/**
* {@inheritdoc}
*/
public function like($field, $pattern)
{
return $this->queryBuilder->expr()->like($this->getFieldName($field), $this->queryBuilder->expr()->literal($pattern));
}
/**
* {@inheritdoc}
*/
public function notLike($field, $pattern)
{
return $this->queryBuilder->expr()->notLike($this->getFieldName($field), $this->queryBuilder->expr()->literal($pattern));
}
/**
* {@inheritdoc}
*/
public function orderBy($field, $direction)
{
return $this->queryBuilder->orderBy($this->getFieldName($field), $direction);
}
/**
* {@inheritdoc}
*/
public function addOrderBy($field, $direction)
{
return $this->queryBuilder->addOrderBy($this->getFieldName($field), $direction);
}
/**
* {@inheritdoc}
*/
private function getFieldName($field)
{
if (false === strpos($field, '.')) {
return $this->queryBuilder->getRootAlias().'.'.$field;
}
return $field;
}
}

View file

@ -0,0 +1,74 @@
SyliusGridBundle [![Build status...](https://secure.travis-ci.org/Sylius/SyliusGridBundle.png?branch=master)](http://travis-ci.org/Sylius/SyliusGridBundle)
================
Advanced grids for your Symfony2 project.
Sylius
------
**Sylius** - Modern ecommerce for Symfony2. Visit [Sylius.org](http://sylius.org).
[phpspec](http://phpspec.net) examples
--------------------------------------
```bash
$ composer install
$ bin/phpspec run -f pretty
```
Documentation
-------------
Documentation is available on [**docs.sylius.org**](http://docs.sylius.org).
Contributing
------------
All informations about contributing to Sylius can be found on [this page](http://docs.sylius.org/en/latest/contributing/index.html).
Mailing lists
-------------
### Users
Questions? Feel free to ask on [users mailing list](http://groups.google.com/group/sylius).
### Developers
To contribute and develop this bundle, use the [developers mailing list](http://groups.google.com/group/sylius-dev).
Sylius twitter account
----------------------
If you want to keep up with updates, [follow the official Sylius account on twitter](http://twitter.com/Sylius).
Bug tracking
------------
This bundle uses [GitHub issues](https://github.com/Sylius/Sylius/issues).
If you have found bug, please create an issue.
Versioning
----------
Releases will be numbered with the format `major.minor.patch`.
And constructed with the following guidelines.
* Breaking backwards compatibility bumps the major.
* New additions without breaking backwards compatibility bumps the minor.
* Bug fixes and misc changes bump the patch.
For more information on SemVer, please visit [semver.org website](http://semver.org/).
This versioning method is same for all **Sylius** bundles and applications.
MIT License
-----------
License can be found [here](https://github.com/Sylius/SyliusGridBundle/blob/master/Resources/meta/LICENSE).
Authors
-------
The bundle was originally created by [Paweł Jędrzejewski](https://twitter.com/pjedrzejewski).
See the list of [contributors](https://github.com/Sylius/SyliusGridBundle/contributors).

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
This file is part of the Sylius package.
(c) Paweł Jędrzejewski
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd"
>
<parameters>
<parameter key="sylius.grid_driver.doctrine.orm.class">Sylius\Bundle\GridBundle\Doctrine\ORM\Driver</parameter>
<parameter key="sylius.grid_driver.doctrine.dbal.class">Sylius\Bundle\GridBundle\Doctrine\DBAL\Driver</parameter>
</parameters>
<services>
<service id="sylius.grid_driver.doctrine.orm" class="%sylius.grid_driver.doctrine.orm.class%">
<argument type="service" id="doctrine.orm.entity_manager" />
<tag name="sylius.grid_driver" alias="doctrine/orm" />
</service>
<service id="sylius.grid_driver.doctrine.dbal" class="%sylius.grid_driver.doctrine.dbal.class%">
<argument type="service" id="doctrine.dbal.default_connection" />
<tag name="sylius.grid_driver" alias="doctrine/dbal" />
</service>
</services>
</container>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
This file is part of the Sylius package.
(c) Paweł Jędrzejewski
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd"
>
<parameters>
<parameter key="sylius.grid_filter.string.class">Sylius\Component\Grid\Filter\StringFilter</parameter>
</parameters>
<services>
<service id="sylius.grid_filter.string" class="%sylius.grid_filter.string.class%">
<tag name="sylius.grid_filter" type="string" />
</service>
</services>
</container>

View file

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
This file is part of the Sylius package.
(c) Paweł Jędrzejewski
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd"
>
<parameters>
<parameter key="sylius.grid.data_provider.class">Sylius\Component\Grid\Data\DataProvider</parameter>
<parameter key="sylius.grid.data_source_provider.class">Sylius\Component\Grid\Data\DataSourceProvider</parameter>
<parameter key="sylius.grid.filters_applicator.class">Sylius\Component\Grid\Filtering\FiltersApplicator</parameter>
<parameter key="sylius.grid.sorter.class">Sylius\Component\Grid\Sorting\Sorter</parameter>
<parameter key="sylius.grid.view_factory.class">Sylius\Component\Grid\View\GridViewFactory</parameter>
<parameter key="sylius.grid.array_to_definition_converter.class">Sylius\Component\Grid\Definition\ArrayToDefinitionConverter</parameter>
<parameter key="sylius.grid.provider.class">Sylius\Component\Grid\Provider\ArrayGridProvider</parameter>
<parameter key="sylius.registry.grid_driver.class">Sylius\Component\Registry\ServiceRegistry</parameter>
<parameter key="sylius.registry.grid_filter.class">Sylius\Component\Registry\ServiceRegistry</parameter>
</parameters>
<services>
<service id="sylius.grid.array_to_definition_converter" class="%sylius.grid.array_to_definition_converter.class%" />
<service id="sylius.grid.provider" class="%sylius.grid.provider.class%">
<argument type="service" id="sylius.grid.array_to_definition_converter" />
<argument>%sylius.grids_definitions%</argument>
</service>
<service id="sylius.grid.view_factory" class="%sylius.grid.view_factory.class%">
<argument type="service" id="sylius.grid.data_provider" />
</service>
<service id="sylius.grid.data_provider" class="%sylius.grid.data_provider.class%">
<argument type="service" id="sylius.grid.data_source_provider" />
<argument type="service" id="sylius.grid.filters_applicator" />
<argument type="service" id="sylius.grid.sorter" />
</service>
<service id="sylius.grid.filters_applicator" class="%sylius.grid.filters_applicator.class%">
<argument type="service" id="sylius.registry.grid_filter" />
</service>
<service id="sylius.grid.sorter" class="%sylius.grid.sorter.class%" />
<service id="sylius.grid.data_source_provider" class="%sylius.grid.data_source_provider.class%">
<argument type="service" id="sylius.registry.grid_driver" />
</service>
<service id="sylius.registry.grid_driver" class="%sylius.registry.grid_driver.class%">
<argument>Sylius\Component\Grid\Data\DriverInterface</argument>
</service>
<service id="sylius.registry.grid_filter" class="%sylius.registry.grid_filter.class%">
<argument>Sylius\Component\Grid\Filtering\FilterInterface</argument>
</service>
</services>
</container>

View file

@ -0,0 +1,19 @@
Copyright (c) 2011-2016 Paweł Jędrzejewski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\GridBundle;
use Sylius\Bundle\GridBundle\DependencyInjection\Compiler\RegisterDriversPass;
use Sylius\Bundle\GridBundle\DependencyInjection\Compiler\RegisterFiltersPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class SyliusGridBundle extends Bundle
{
/**
* {@inheritdoc}
*/
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new RegisterDriversPass());
$container->addCompilerPass(new RegisterFiltersPass());
}
}

View file

@ -0,0 +1,38 @@
{
"name": "sylius/grid-bundle",
"type": "symfony-bundle",
"description": "Amazing grids with support of filters and custom fields integrated into Symfony.",
"keywords": ["shop", "ecommerce", "store", "webshop", "sylius", "grid", "admin", "crud"],
"homepage": "http://sylius.org",
"license": "MIT",
"authors": [
{
"name": "Paweł Jędrzejewski",
"homepage": "http://pjedrzejewski.com"
},
{
"name": "Sylius project",
"homepage": "http://sylius.org"
},
{
"name": "Community contributions",
"homepage": "http://github.com/Sylius/Sylius/contributors"
}
],
"require": {
"php": ">=5.3.3",
"sylius/resource-bundle": "0.14.*@dev"
},
"config": {
"bin-dir": "bin"
},
"autoload": {
"psr-4": { "Sylius\\Bundle\\GridBundle\\": "" }
},
"extra": {
"branch-alias": {
"dev-master": "0.14-dev"
}
}
}

View file

@ -0,0 +1,5 @@
suites:
main:
namespace: Sylius\Bundle\GridBundle
psr4_prefix: Sylius\Bundle\GridBundle
src_path: .

View file

@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Bundle\GridBundle\Doctrine\ORM;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Bundle\GridBundle\Doctrine\ORM\DataSource;
use Sylius\Bundle\GridBundle\Doctrine\ORM\Driver;
use Sylius\Component\Grid\Data\DriverInterface;
use Sylius\Component\Grid\Parameters;
/**
* @mixin Driver
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class DriverSpec extends ObjectBehavior
{
function let(EntityManagerInterface $entityManager)
{
$this->beConstructedWith($entityManager);
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Bundle\GridBundle\Doctrine\ORM\Driver');
}
function it_implements_grid_driver()
{
$this->shouldImplement(DriverInterface::class);
}
function it_throws_exception_if_class_is_undefined(Parameters $parameters)
{
$this
->shouldThrow(\InvalidArgumentException::class)
->during('getDataSource', array(array(), $parameters));
;
}
function it_creates_data_source_via_doctrine_orm_query_builder(
EntityManagerInterface $entityManager,
EntityRepository $entityRepository,
QueryBuilder $queryBuilder,
Parameters $parameters
) {
$entityManager->getRepository('App:Book')->willReturn($entityRepository);
$entityRepository->createQueryBuilder('o')->willReturn($queryBuilder);
$this->getDataSource(array('class' => 'App:Book'), $parameters)->shouldHaveType(DataSource::class);
}
}

View file

@ -535,4 +535,26 @@ class RequestConfiguration
return $parameters;
}
/**
* @return bool
*/
public function hasGrid()
{
return $this->parameters->has('grid');
}
/**
* @return string
*
* @throws \LogicException
*/
public function getGrid()
{
if (!$this->hasGrid()) {
throw new \LogicException('Current action does not use grid.');
}
return $this->parameters->get('grid');
}
}

View file

@ -14,6 +14,10 @@ namespace Sylius\Bundle\ResourceBundle\Controller;
use Hateoas\Configuration\Route;
use Hateoas\Representation\Factory\PagerfantaFactory;
use Pagerfanta\Pagerfanta;
use Sylius\Component\Grid\Parameters as GridParameters;
use Sylius\Component\Grid\Provider\GridProviderInterface;
use Sylius\Component\Grid\View\GridView;
use Sylius\Component\Grid\View\GridViewFactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
/**
@ -27,11 +31,25 @@ class ResourcesCollectionProvider implements ResourcesCollectionProviderInterfac
private $pagerfantaRepresentationFactory;
/**
* @param PagerfantaFactory $pagerfantaRepresentationFactory
* @var GridProviderInterface
*/
public function __construct(PagerfantaFactory $pagerfantaRepresentationFactory)
private $gridProvider;
/**
* @var GridViewFactoryInterface
*/
private $gridViewFactory;
/**
* @param PagerfantaFactory $pagerfantaRepresentationFactory
* @param GridProviderInterface $gridProvider
* @param GridViewFactoryInterface $gridViewFactory
*/
public function __construct(PagerfantaFactory $pagerfantaRepresentationFactory, GridProviderInterface $gridProvider, GridViewFactoryInterface $gridViewFactory)
{
$this->pagerfantaRepresentationFactory = $pagerfantaRepresentationFactory;
$this->gridProvider = $gridProvider;
$this->gridViewFactory = $gridViewFactory;
}
/**
@ -45,12 +63,10 @@ class ResourcesCollectionProvider implements ResourcesCollectionProviderInterfac
$request = $requestConfiguration->getRequest();
$resources->setMaxPerPage($requestConfiguration->getPaginationMaxPerPage());
$resources->setCurrentPage($request->query->get('page', 1));
}
if (!$requestConfiguration->isHtmlRequest()) {
$route = new Route($request->attributes->get('_route'), array_merge($request->attributes->get('_route_params'), $request->query->all()));
return $this->pagerfantaRepresentationFactory->createRepresentation($resources, $route);
}
if (!$requestConfiguration->isHtmlRequest() && $resources instanceof Pagerfanta) {
return $this->createPaginatedRepresentation($requestConfiguration, $resources);
}
return $resources;
@ -64,6 +80,10 @@ class ResourcesCollectionProvider implements ResourcesCollectionProviderInterfac
*/
private function getResources(RequestConfiguration $requestConfiguration, RepositoryInterface $repository)
{
if ($requestConfiguration->hasGrid()) {
return $this->getGrid($requestConfiguration);
}
if (null !== $repositoryMethod = $requestConfiguration->getRepositoryMethod()) {
$callable = [$repository, $repositoryMethod];
@ -82,4 +102,39 @@ class ResourcesCollectionProvider implements ResourcesCollectionProviderInterfac
return $repository->createPaginator($requestConfiguration->getCriteria(), $requestConfiguration->getSorting());
}
/**
* @param RequestConfiguration $requestConfiguration
*
* @return mixed
*/
private function getGrid(RequestConfiguration $requestConfiguration)
{
$gridDefinition = $this->gridProvider->get($requestConfiguration->getGrid());
$request = $requestConfiguration->getRequest();
$parameters = new GridParameters($request->query->all());
$gridView = $this->gridViewFactory->create($gridDefinition, $parameters);
if ($requestConfiguration->isHtmlRequest()) {
return $gridView;
}
return $gridView->getData();
}
/**
* @param RequestConfiguration $requestConfiguration
* @param Pagerfanta $paginator
*
* @return PaginatedRepresentation
*/
private function createPaginatedRepresentation(RequestConfiguration $requestConfiguration, Pagerfanta $paginator)
{
$request = $requestConfiguration->getRequest();
$route = new Route($request->attributes->get('_route'), array_merge($request->attributes->get('_route_params'), $request->query->all()));
return $this->pagerfantaRepresentationFactory->createRepresentation($paginator, $route);
}
}

View file

@ -47,6 +47,8 @@
<service id="sylius.resource_controller.pagerfanta_representation_factory" class="%sylius.resource_controller.pagerfanta_representation_factory.class%" />
<service id="sylius.resource_controller.resources_collection_provider" class="%sylius.resource_controller.resources_collection_provider.class%">
<argument type="service" id="sylius.resource_controller.pagerfanta_representation_factory" />
<argument type="service" id="sylius.grid.provider" />
<argument type="service" id="sylius.grid.view_factory" />
</service>
<service id="sylius.resource_controller.form_factory" class="%sylius.resource_controller.form_factory.class%">
<argument type="service" id="form.factory" />

View file

@ -35,6 +35,7 @@ class Configuration implements ConfigurationInterface
->scalarNode('section')->cannotBeEmpty()->end()
->scalarNode('redirect')->cannotBeEmpty()->end()
->scalarNode('templates')->cannotBeEmpty()->end()
->scalarNode('grid')->cannotBeEmpty()->end()
->arrayNode('except')
->prototype('scalar')->end()
->end()

View file

@ -77,27 +77,27 @@ class ResourceLoader implements LoaderInterface
$rootPath = sprintf('/%s/', isset($configuration['path']) ? $configuration['path'] : Urlizer::urlize($metadata->getPluralName()));
if (in_array('index', $routesToGenerate)) {
$indexRoute = $this->createRoute($metadata, $configuration, $rootPath, 'index', ['GET']);
$indexRoute = $this->createRoute($metadata, $configuration, $rootPath, 'index', ['GET'], $isApi);
$routes->add($this->getRouteName($metadata, $configuration, 'index'), $indexRoute);
}
if (in_array('create', $routesToGenerate)) {
$createRoute = $this->createRoute($metadata, $configuration, $isApi ? $rootPath : $rootPath.'new', 'create', $isApi ? ['POST'] : ['GET', 'POST']);
$createRoute = $this->createRoute($metadata, $configuration, $isApi ? $rootPath : $rootPath . 'new', 'create', $isApi ? ['POST'] : ['GET', 'POST'], $isApi);
$routes->add($this->getRouteName($metadata, $configuration, 'create'), $createRoute);
}
if (in_array('update', $routesToGenerate)) {
$updateRoute = $this->createRoute($metadata, $configuration, $isApi ? $rootPath.'{id}' : $rootPath.'{id}/edit', 'update', $isApi ? ['PUT', 'PATCH'] : ['GET', 'PUT', 'PATCH']);
$updateRoute = $this->createRoute($metadata, $configuration, $isApi ? $rootPath . '{id}' : $rootPath . '{id}/edit', 'update', $isApi ? ['PUT', 'PATCH'] : ['GET', 'PUT', 'PATCH'], $isApi);
$routes->add($this->getRouteName($metadata, $configuration, 'update'), $updateRoute);
}
if (in_array('show', $routesToGenerate)) {
$showRoute = $this->createRoute($metadata, $configuration, $rootPath.'{id}', 'show', ['GET']);
$showRoute = $this->createRoute($metadata, $configuration, $rootPath . '{id}', 'show', ['GET'], $isApi);
$routes->add($this->getRouteName($metadata, $configuration, 'show'), $showRoute);
}
if (in_array('delete', $routesToGenerate)) {
$deleteRoute = $this->createRoute($metadata, $configuration, $rootPath.'{id}', 'delete', ['DELETE']);
$deleteRoute = $this->createRoute($metadata, $configuration, $rootPath . '{id}', 'delete', array('DELETE'), $isApi);
$routes->add($this->getRouteName($metadata, $configuration, 'delete'), $deleteRoute);
}
@ -137,12 +137,21 @@ class ResourceLoader implements LoaderInterface
*
* @return Route
*/
private function createRoute(MetadataInterface $metadata, array $configuration, $path, $actionName, array $methods)
private function createRoute(MetadataInterface $metadata, array $configuration, $path, $actionName, array $methods, $isApi = false)
{
$defaults = [
'_controller' => $metadata->getServiceId('controller').sprintf(':%sAction', $actionName),
];
if ($isApi && 'index' === $actionName) {
$defaults['_sylius']['serialization_groups'] = ['Default'];
}
if ($isApi && in_array($actionName, ['show', 'create', 'update'])) {
$defaults['_sylius']['serialization_groups'] = ['Default', 'Detailed'];
}
if (isset($configuration['grid']) && 'index' === $actionName) {
$defaults['_sylius']['grid'] = $configuration['grid'];
}
if (isset($configuration['form']) && in_array($actionName, ['create', 'update'])) {
$defaults['_sylius']['form'] = $configuration['form'];
}

View file

@ -473,10 +473,34 @@ class RequestConfigurationSpec extends ObjectBehavior
$parameters->get('section')->willReturn('admin');
$this->getSection()->shouldReturn('admin');
}
function it_has_vars(Parameters $parameters)
{
$parameters->get('vars', [])->willReturn(['foo' => 'bar']);
$this->getVars()->shouldReturn(['foo' => 'bar']);
}
function it_does_not_have_grid_unless_defined_as_in_parameters(Parameters $parameters)
{
$parameters->has('grid')->willReturn(false);
$this->shouldNotHaveGrid();
$parameters->has('grid')->willReturn(true);
$this->shouldHaveGrid();
$parameters->has('grid')->willReturn(true);
$parameters->get('grid')->willReturn('sylius_admin_tax_category');
$this->getGrid()->shouldReturn('sylius_admin_tax_category');
}
function it_throws_an_exception_when_trying_to_retrieve_undefined_grid(Parameters $parameters)
{
$parameters->has('grid')->willReturn(false);
$this
->shouldThrow(\LogicException::class)
->during('getGrid')
;
}
}

View file

@ -20,6 +20,11 @@ use Prophecy\Argument;
use Sylius\Bundle\ResourceBundle\Controller\RequestConfiguration;
use Sylius\Bundle\ResourceBundle\Controller\ResourcesCollectionProvider;
use Sylius\Bundle\ResourceBundle\Controller\ResourcesCollectionProviderInterface;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
use Sylius\Component\Grid\Provider\GridProviderInterface;
use Sylius\Component\Grid\View\GridView;
use Sylius\Component\Grid\View\GridViewFactoryInterface;
use Sylius\Component\Resource\Model\ResourceInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Symfony\Component\HttpFoundation\ParameterBag;
@ -32,9 +37,9 @@ use Symfony\Component\HttpFoundation\Request;
*/
class ResourcesCollectionProviderSpec extends ObjectBehavior
{
function let(PagerfantaFactory $pagerfantaRepresentationFactory)
function let(PagerfantaFactory $pagerfantaRepresentationFactory, GridProviderInterface $gridProvider, GridViewFactoryInterface $gridViewFactory)
{
$this->beConstructedWith($pagerfantaRepresentationFactory);
$this->beConstructedWith($pagerfantaRepresentationFactory, $gridProvider, $gridViewFactory);
}
function it_is_initializable()
@ -53,6 +58,7 @@ class ResourcesCollectionProviderSpec extends ObjectBehavior
ResourceInterface $firstResource,
ResourceInterface $secondResource
) {
$requestConfiguration->hasGrid()->willReturn(false);
$requestConfiguration->isHtmlRequest()->willReturn(true);
$requestConfiguration->getRepositoryMethod(null)->willReturn(null);
@ -71,6 +77,7 @@ class ResourcesCollectionProviderSpec extends ObjectBehavior
ResourceInterface $secondResource,
ResourceInterface $thirdResource
) {
$requestConfiguration->hasGrid()->willReturn(false);
$requestConfiguration->isHtmlRequest()->willReturn(true);
$requestConfiguration->getRepositoryMethod(null)->willReturn(null);
@ -91,6 +98,7 @@ class ResourcesCollectionProviderSpec extends ObjectBehavior
RepositoryInterface $repository,
ResourceInterface $firstResource
) {
$requestConfiguration->hasGrid()->willReturn(false);
$requestConfiguration->isHtmlRequest()->willReturn(true);
$requestConfiguration->getRepositoryMethod()->willReturn('findAll');
$requestConfiguration->getRepositoryArguments()->willReturn(['foo']);
@ -111,6 +119,7 @@ class ResourcesCollectionProviderSpec extends ObjectBehavior
Request $request,
ParameterBag $queryParameters
) {
$requestConfiguration->hasGrid()->willReturn(false);
$requestConfiguration->isHtmlRequest()->willReturn(true);
$requestConfiguration->getRepositoryMethod()->willReturn(null);
@ -142,6 +151,7 @@ class ResourcesCollectionProviderSpec extends ObjectBehavior
PagerfantaFactory $pagerfantaRepresentationFactory,
PaginatedRepresentation $paginatedRepresentation
) {
$requestConfiguration->hasGrid()->willReturn(false);
$requestConfiguration->isHtmlRequest()->willReturn(false);
$requestConfiguration->getRepositoryMethod()->willReturn(null);
@ -176,6 +186,7 @@ class ResourcesCollectionProviderSpec extends ObjectBehavior
Request $request,
ParameterBag $queryParameters
) {
$requestConfiguration->hasGrid()->willReturn(false);
$requestConfiguration->isHtmlRequest()->willReturn(true);
$requestConfiguration->getRepositoryMethod()->willReturn('findAll');
$requestConfiguration->getRepositoryArguments()->willReturn(['foo']);
@ -207,6 +218,7 @@ class ResourcesCollectionProviderSpec extends ObjectBehavior
PagerfantaFactory $pagerfantaRepresentationFactory,
PaginatedRepresentation $paginatedRepresentation
) {
$requestConfiguration->hasGrid()->willReturn(false);
$requestConfiguration->isHtmlRequest()->willReturn(false);
$requestConfiguration->getRepositoryMethod()->willReturn('findAll');
$requestConfiguration->getRepositoryArguments()->willReturn(['foo']);
@ -234,4 +246,46 @@ class ResourcesCollectionProviderSpec extends ObjectBehavior
$this->get($requestConfiguration, $repository)->shouldReturn($paginatedRepresentation);
}
function it_creates_a_grid_view_if_needed(
Grid $grid,
GridProviderInterface $gridProvider,
GridView $gridView,
GridViewFactoryInterface $gridViewFactory,
Pagerfanta $paginator,
PagerfantaFactory $pagerfantaRepresentationFactory,
PaginatedRepresentation $paginatedRepresentation,
ParameterBag $queryParameters,
ParameterBag $requestAttributes,
RepositoryInterface $repository,
Request $request,
RequestConfiguration $requestConfiguration
)
{
$requestConfiguration->hasGrid()->willReturn(true);
$requestConfiguration->getGrid()->willReturn('sylius_admin_product');
$requestConfiguration->isHtmlRequest()->willReturn(false);
$requestConfiguration->getPaginationMaxPerPage()->willReturn(5);
$requestConfiguration->getRequest()->willReturn($request);
$request->query = $queryParameters;
$queryParameters->get('page', 1)->willReturn(6);
$queryParameters->all()->willReturn(array('foo' => 2, 'bar' => 15));
$request->attributes = $requestAttributes;
$requestAttributes->get('_route')->willReturn('sylius_product_index');
$requestAttributes->get('_route_params')->willReturn(array('slug' => 'foo-bar'));
$gridProvider->get('sylius_admin_product')->willReturn($grid);
$gridViewFactory->create($grid, Argument::type(Parameters::class))->willReturn($gridView);
$gridView->getData()->willReturn($paginator);
$paginator->setMaxPerPage(5)->shouldBeCalled();
$paginator->setCurrentPage(6)->shouldBeCalled();
$pagerfantaRepresentationFactory->createRepresentation($paginator, Argument::type(Route::class))->willReturn($paginatedRepresentation);
$this->get($requestConfiguration, $repository)->shouldReturn($paginatedRepresentation);
}
}

View file

@ -53,8 +53,7 @@ EOT;
$this
->shouldThrow(InvalidConfigurationException::class)
->during('load', [$configuration, 'sylius.resource'])
;
->during('load', [$configuration, 'sylius.resource']);
}
function it_throws_an_exception_if_invalid_resource_configured(RegistryInterface $resourceRegistry)
@ -68,8 +67,7 @@ EOT;
$this
->shouldThrow(\InvalidArgumentException::class)
->during('load', [$configuration, 'sylius.resource'])
;
->during('load', [$configuration, 'sylius.resource']);
}
function it_generates_routing_based_on_resource_configuration(
@ -82,7 +80,8 @@ EOT;
Route $createRoute,
Route $updateRoute,
Route $deleteRoute
) {
)
{
$resourceRegistry->get('sylius.product')->willReturn($metadata);
$metadata->getApplicationName()->willReturn('sylius');
$metadata->getName()->willReturn('product');
@ -139,7 +138,8 @@ EOT;
Route $createRoute,
Route $updateRoute,
Route $deleteRoute
) {
)
{
$resourceRegistry->get('sylius.product_option')->willReturn($metadata);
$metadata->getApplicationName()->willReturn('sylius');
$metadata->getName()->willReturn('product_option');
@ -196,7 +196,8 @@ EOT;
Route $createRoute,
Route $updateRoute,
Route $deleteRoute
) {
)
{
$resourceRegistry->get('sylius.product')->willReturn($metadata);
$metadata->getApplicationName()->willReturn('sylius');
$metadata->getName()->willReturn('product');
@ -254,7 +255,8 @@ EOT;
Route $createRoute,
Route $updateRoute,
Route $deleteRoute
) {
)
{
$resourceRegistry->get('sylius.product')->willReturn($metadata);
$metadata->getApplicationName()->willReturn('sylius');
$metadata->getName()->willReturn('product');
@ -318,7 +320,8 @@ EOT;
Route $createRoute,
Route $updateRoute,
Route $deleteRoute
) {
)
{
$resourceRegistry->get('sylius.product')->willReturn($metadata);
$metadata->getApplicationName()->willReturn('sylius');
$metadata->getName()->willReturn('product');
@ -391,7 +394,8 @@ EOT;
Route $createRoute,
Route $updateRoute,
Route $deleteRoute
) {
)
{
$resourceRegistry->get('sylius.product')->willReturn($metadata);
$metadata->getApplicationName()->willReturn('sylius');
$metadata->getName()->willReturn('product');
@ -459,7 +463,8 @@ EOT;
Route $indexRoute,
Route $createRoute,
Route $updateRoute
) {
)
{
$resourceRegistry->get('sylius.product')->willReturn($metadata);
$metadata->getApplicationName()->willReturn('sylius');
$metadata->getName()->willReturn('product');
@ -502,7 +507,8 @@ EOT;
RouteCollection $routeCollection,
Route $indexRoute,
Route $createRoute
) {
)
{
$resourceRegistry->get('sylius.product')->willReturn($metadata);
$metadata->getApplicationName()->willReturn('sylius');
$metadata->getName()->willReturn('product');
@ -543,8 +549,7 @@ EOT;
$this
->shouldThrow(\InvalidArgumentException::class)
->during('load', [$configuration, 'sylius.resource'])
;
->during('load', [$configuration, 'sylius.resource']);
}
function it_generates_routing_with_custom_redirect_if_specified(
@ -637,24 +642,36 @@ EOT;
$showDefaults = [
'_controller' => 'sylius.controller.product:showAction',
'_sylius' => [
'serialization_groups' => ['Default', 'Detailed']
]
];
$routeFactory->createRoute('/products/{id}', $showDefaults, [], [], '', [], ['GET'])->willReturn($showRoute);
$routeCollection->add('sylius_product_show', $showRoute)->shouldBeCalled();
$indexDefaults = [
'_controller' => 'sylius.controller.product:indexAction',
'_sylius' => [
'serialization_groups' => ['Default']
]
];
$routeFactory->createRoute('/products/', $indexDefaults, [], [], '', [], ['GET'])->willReturn($indexRoute);
$routeCollection->add('sylius_product_index', $indexRoute)->shouldBeCalled();
$createDefaults = [
'_controller' => 'sylius.controller.product:createAction',
'_sylius' => [
'serialization_groups' => ['Default', 'Detailed']
]
];
$routeFactory->createRoute('/products/', $createDefaults, [], [], '', [], ['POST'])->willReturn($createRoute);
$routeCollection->add('sylius_product_create', $createRoute)->shouldBeCalled();
$updateDefaults = [
'_controller' => 'sylius.controller.product:updateAction',
'_sylius' => [
'serialization_groups' => ['Default', 'Detailed']
]
];
$routeFactory->createRoute('/products/{id}', $updateDefaults, [], [], '', [], ['PUT', 'PATCH'])->willReturn($updateRoute);
$routeCollection->add('sylius_product_update', $updateRoute)->shouldBeCalled();
@ -668,6 +685,48 @@ EOT;
$this->load($configuration, 'sylius.resource_api')->shouldReturn($routeCollection);
}
function it_configures_grid_for_index_action_if_specified(
RegistryInterface $resourceRegistry,
MetadataInterface $metadata,
RouteFactoryInterface $routeFactory,
RouteCollection $routeCollection,
Route $indexRoute,
Route $createRoute
)
{
$resourceRegistry->get('sylius.product')->willReturn($metadata);
$metadata->getApplicationName()->willReturn('sylius');
$metadata->getName()->willReturn('product');
$metadata->getPluralName()->willReturn('products');
$metadata->getServiceId('controller')->willReturn('sylius.controller.product');
$routeFactory->createRouteCollection()->willReturn($routeCollection);
$configuration =
<<<EOT
alias: sylius.product
only: ['create', 'index']
grid: sylius_admin_product
EOT;
$indexDefaults = array(
'_controller' => 'sylius.controller.product:indexAction',
'_sylius' => array(
'grid' => 'sylius_admin_product',
)
);
$routeFactory->createRoute('/products/', $indexDefaults, array(), array(), '', array(), array('GET'))->willReturn($indexRoute);
$routeCollection->add('sylius_product_index', $indexRoute)->shouldBeCalled();
$createDefaults = array(
'_controller' => 'sylius.controller.product:createAction'
);
$routeFactory->createRoute('/products/new', $createDefaults, array(), array(), '', array(), array('GET', 'POST'))->willReturn($createRoute);
$routeCollection->add('sylius_product_create', $createRoute)->shouldBeCalled();
$this->load($configuration, 'sylius.resource')->shouldReturn($routeCollection);
}
function it_generates_routing_with_custom_variables(
RegistryInterface $resourceRegistry,
MetadataInterface $metadata,
@ -678,7 +737,8 @@ EOT;
Route $createRoute,
Route $updateRoute,
Route $deleteRoute
) {
)
{
$resourceRegistry->get('sylius.product')->willReturn($metadata);
$metadata->getApplicationName()->willReturn('sylius');
$metadata->getName()->willReturn('product');

View file

@ -88,8 +88,13 @@ sylius_backend_tax_rate:
type: sylius.resource
sylius_backend_shipping_category:
resource: @SyliusWebBundle/Resources/config/routing/backend/shipping_category.yml
prefix: /shipping-categories
resource: |
alias: sylius.shipping_category
section: backend
except: ['show']
templates: SyliusWebBundle:Backend/ShippingCategory
grid: sylius_admin_shipping_category
type: sylius.resource
sylius_backend_shipping_method:
resource: @SyliusWebBundle/Resources/config/routing/backend/shipping_method.yml

View file

@ -20,8 +20,8 @@
<h1><i class="glyphicon glyphicon-cog"></i> {{ 'sylius.shipping_category.index_header'|trans|raw }}</h1>
</div>
{{ pagination(shipping_categories) }}
{{ list(shipping_categories) }}
{{ pagination(shipping_categories) }}
{{ pagination(shipping_categories.data) }}
{{ list(shipping_categories.data) }}
{{ pagination(shipping_categories.data) }}
{% endblock %}

5
src/Sylius/Component/Grid/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
vendor/
bin/
composer.phar
composer.lock

View file

@ -0,0 +1,10 @@
language: php
php:
- 5.5
- 5.6
- 7.0
before_script: composer install --no-interaction --prefer-source
script: bin/phpspec run -fpretty --verbose

View file

@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Data;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Filtering\FiltersApplicatorInterface;
use Sylius\Component\Grid\Parameters;
use Sylius\Component\Grid\Sorting\SorterInterface;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class DataProvider implements DataProviderInterface
{
/**
* @var DataSourceProviderInterface
*/
private $dataSourceProvider;
/**
* @var FiltersApplicatorInterface
*/
private $filtersApplicator;
/**
* @var SorterInterface
*/
private $sorter;
/**
* @param DataSourceProviderInterface $dataSourceProvider
* @param FiltersApplicatorInterface $filtersApplicator
* @param SorterInterface $sorter
*/
public function __construct(DataSourceProviderInterface $dataSourceProvider, FiltersApplicatorInterface $filtersApplicator, SorterInterface $sorter)
{
$this->dataSourceProvider = $dataSourceProvider;
$this->filtersApplicator = $filtersApplicator;
$this->sorter = $sorter;
}
/**
* {@inheritdoc}
*/
public function getData(Grid $grid, Parameters $parameters)
{
$dataSource = $this->dataSourceProvider->getDataSource($grid, $parameters);
$this->filtersApplicator->apply($dataSource, $grid, $parameters);
$this->sorter->sort($dataSource, $grid, $parameters);
return $dataSource->getData($parameters);
}
}

View file

@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Data;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
interface DataProviderInterface
{
/**
* @param Grid $grid
* @param array $parameters
*
* @return mixed
*/
public function getData(Grid $grid, Parameters $parameters);
}

View file

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Data;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@svaluelius.org>
*/
interface DataSourceInterface
{
const CONDITION_AND = 'and';
const CONDITION_OR = 'or';
/**
* @param mixed $expression
* @param string $condition
*
* @return mixed
*/
public function restrict($expression, $condition = self::CONDITION_AND);
/**
* @return ExpressionBuilderInterface
*/
public function getExpressionBuilder();
/**
* @param Parameters $parameters
*
* @return mixed
*/
public function getData(Parameters $parameters);
}

View file

@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Data;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
use Sylius\Component\Registry\ServiceRegistryInterface;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class DataSourceProvider implements DataSourceProviderInterface
{
/**
* @var ServiceRegistryInterface
*/
private $driversRegistry;
/**
* @param ServiceRegistryInterface $driversRegistry
*/
public function __construct(ServiceRegistryInterface $driversRegistry)
{
$this->driversRegistry = $driversRegistry;
}
/**
* {@inheritdoc}
*/
public function getDataSource(Grid $grid, Parameters $parameters)
{
if (!$this->driversRegistry->has($driver = $grid->getDriver())) {
throw new UnsupportedDriverException($driver);
}
return $this->driversRegistry->get($driver)->getDataSource($grid->getDriverConfiguration(), $parameters);
}
}

View file

@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Data;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
interface DataSourceProviderInterface
{
/**
* @param Grid $grid
* @param Parameters $parameters
*
* @return DataSourceInterface
*/
public function getDataSource(Grid $grid, Parameters $parameters);
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Data;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
interface DriverInterface
{
/**
* @param array $configuration
* @param Parameters $parameters
*
* @return DataSourceInterface
*/
public function getDataSource(array $configuration, Parameters $parameters);
}

View file

@ -0,0 +1,151 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Data;
/**
* @author Paweł Jędrzejewski <pawel@svaluelius.org>
*/
interface ExpressionBuilderInterface
{
/**
* @param mixed $expressions
*
* @return self
*/
public function andX($expressions);
/**
* @param mixed $expressions
*
* @return self
*/
public function orX($expressions);
/**
* @param string $field
* @param string $operator
* @param mixed $value
*
* @return self
*/
public function comparison($field, $operator, $value);
/**
* @param string $field
* @param mixed $value
*
* @return self
*/
public function equals($field, $value);
/**
* @param string $field
* @param mixed $value
*
* @return self
*/
public function notEquals($field, $value);
/**
* @param string $field
* @param mixed $value
*
* @return self
*/
public function lessThan($field, $value);
/**
* @param string $field
* @param mixed $value
*
* @return self
*/
public function lessThanOrEqual($field, $value);
/**
* @param string $field
* @param mixed $value
*
* @return self
*/
public function greaterThan($field, $value);
/**
* @param string $field
* @param mixed $value
*
* @return self
*/
public function greaterThanOrEqual($field, $value);
/**
* @param string $field
* @param array $values
*
* @return self
*/
public function in($field, array $values);
/**
* @param string $field
* @param array $values
*
* @return self
*/
public function notIn($field, array $values);
/**
* @param string $field
*
* @return self
*/
public function isNull($field);
/**
* @param string $field
*
* @return self
*/
public function isNotNull($field);
/**
* @param string $field
* @param string $pattern
*
* @return self
*/
public function like($field, $pattern);
/**
* @param string $field
* @param string $pattern
*
* @return self
*/
public function notLike($field, $pattern);
/**
* @param string $field
* @param string $direction
*
* @return self
*/
public function orderBy($field, $direction);
/**
* @param string $field
* @param string $direction
*
* @return self
*/
public function addOrderBy($field, $direction);
}

View file

@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Data;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class UnsupportedDriverException extends \InvalidArgumentException
{
/**
* @param string $name
*/
public function __construct($name)
{
parent::__construct(sprintf('Grid data driver "%s" is not supported.', $name));
}
}

View file

@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Definition;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class Action
{
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $type;
/**
* @var string
*/
private $label;
/**
* @param string $name
* @param string $type
*/
private function __construct($name, $type)
{
$this->name = $name;
$this->type = $type;
$this->label = $name;
}
/**
* @param string $name
* @param string $type
*
* @return Action
*/
public static function fromNameAndType($name, $type)
{
$field = new Action($name, $type);
return $field;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return string
*/
public function getLabel()
{
return $this->label;
}
/**
* @param string $label
*/
public function setLabel($label)
{
$this->label = $label;
}
}

View file

@ -0,0 +1,94 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Definition;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class ActionGroup
{
/**
* @var string
*/
private $name;
/**
* @var array
*/
private $actions = array();
/**
* @param string $name
*/
private function __construct($name)
{
$this->name = $name;
}
/**
* @param string $name
*/
public static function named($name)
{
$actionGroup = new ActionGroup($name);
return $actionGroup;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return array
*/
public function getActions()
{
return $this->actions;
}
/**
* @param Action $action
*/
public function addAction(Action $action)
{
if ($this->hasAction($name = $action->getName())) {
throw new \InvalidArgumentException(sprintf('Action "%s" already exists.', $name));
}
$this->actions[$name] = $action;
}
/**
* @param string $name
*/
public function getAction($name)
{
if (!$this->hasAction($name)) {
throw new \InvalidArgumentException(sprintf('Action "%s" does not exist.', $name));
}
return $this->actions[$name];
}
/**
* @param string $name
*/
public function hasAction($name)
{
return array_key_exists($name, $this->actions);
}
}

View file

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Definition;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class ArrayToDefinitionConverter implements ArrayToDefinitionConverterInterface
{
/**
* {@inheritdoc}
*/
public function convert($code, array $configuration)
{
$grid = Grid::fromCodeAndDriverConfiguration($code, $configuration['driver']['name'], $configuration['driver']['options']);
if (array_key_exists('sorting', $configuration)) {
$grid->setSorting($configuration['sorting']);
}
foreach ($configuration['fields'] as $name => $fieldConfiguration) {
$field = Field::fromNameAndType($name, $fieldConfiguration['type']);
if (array_key_exists('path', $fieldConfiguration)) {
$field->setPath($fieldConfiguration['path']);
}
if (array_key_exists('label', $fieldConfiguration)) {
$field->setLabel($fieldConfiguration['label']);
}
$grid->addField($field);
}
foreach ($configuration['filters'] as $name => $filterConfiguration) {
$filter = Filter::fromNameAndType($name, $filterConfiguration['type']);
if (array_key_exists('label', $filterConfiguration)) {
$filter->setLabel($filterConfiguration['label']);
}
$grid->addFilter($filter);
}
foreach ($configuration['actions'] as $groupName => $actions) {
$actionGroup = ActionGroup::named($groupName);
foreach ($actions as $name => $actionConfiguration) {
$action = Action::fromNameAndType($name, $actionConfiguration['type']);
if (array_key_exists('label', $actionConfiguration)) {
$action->setLabel($actionConfiguration['label']);
}
$actionGroup->addAction($action);
}
$grid->addActionGroup($actionGroup);
}
return $grid;
}
}

View file

@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Definition;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
interface ArrayToDefinitionConverterInterface
{
/**
* @param string $code
* @param array $configuration
*
* @return Grid
*/
public function convert($code, array $configuration);
}

View file

@ -0,0 +1,133 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Definition;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class Field
{
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $type;
/**
* @var string
*/
private $path;
/**
* @var string
*/
private $label;
/**
* @var array
*/
private $options = array();
/**
* @param string $name
* @param string $type
*/
private function __construct($name, $type)
{
$this->name = $name;
$this->type = $type;
$this->path = $name;
$this->label = $name;
}
/**
* @param string $name
* @param string $type
*
* @return Field
*/
public static function fromNameAndType($name, $type)
{
$field = new Field($name, $type);
return $field;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* @param string $path
*/
public function setPath($path)
{
$this->path = $path;
}
/**
* @return string
*/
public function getLabel()
{
return $this->label;
}
/**
* @param string $label
*/
public function setLabel($label)
{
$this->label = $label;
}
/**
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* @param array $options
*/
public function setOptions($options)
{
$this->options = $options;
}
}

View file

@ -0,0 +1,111 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Definition;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class Filter
{
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $type;
/**
* @var string
*/
private $label;
/**
* @var array
*/
private $options = array();
/**
* @param string $name
* @param string $type
*/
private function __construct($name, $type)
{
$this->name = $name;
$this->type = $type;
$this->label = $name;
}
/**
* @param string $name
* @param string $type
*
* @return Filter
*/
public static function fromNameAndType($name, $type)
{
$field = new Filter($name, $type);
return $field;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return string
*/
public function getLabel()
{
return $this->label;
}
/**
* @param string $label
*/
public function setLabel($label)
{
$this->label = $label;
}
/**
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* @param array $options
*/
public function setOptions($options)
{
$this->options = $options;
}
}

View file

@ -0,0 +1,241 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Definition;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class Grid
{
/**
* @var string
*/
private $code;
/**
* @var string
*/
private $driver;
/**
* @var array
*/
private $driverConfiguration;
/**
* @var array
*/
private $sorting = array();
/**
* @var array
*/
private $fields = array();
/**
* @var array
*/
private $filters = array();
/**
* @var array
*/
private $actionGroups = array();
/**
* @param string $code
* @param string $driver
* @param array $driverConfiguration
*/
private function __construct($code, $driver, array $driverConfiguration)
{
$this->code = $code;
$this->driver = $driver;
$this->driverConfiguration = $driverConfiguration;
}
/**
* @param string $code
* @param string $driver
* @param array $driverConfiguration
*
* @return Grid
*/
public static function fromCodeAndDriverConfiguration($code, $driver, array $driverConfiguration)
{
$grid = new Grid($code, $driver, $driverConfiguration);
return $grid;
}
/**
* @return string
*/
public function getCode()
{
return $this->code;
}
/**
* @return string
*/
public function getDriver()
{
return $this->driver;
}
/**
* @return array
*/
public function getDriverConfiguration()
{
return $this->driverConfiguration;
}
/**
* @return array
*/
public function getSorting()
{
return $this->sorting;
}
/**
* @param array $sorting
*/
public function setSorting($sorting)
{
$this->sorting = $sorting;
}
/**
* @return array
*/
public function getFields()
{
return $this->fields;
}
/**
* @param Field $field
*/
public function addField(Field $field)
{
if ($this->hasField($name = $field->getName())) {
throw new \InvalidArgumentException(sprintf('Field "%s" already exists.', $name));
}
$this->fields[$name] = $field;
}
/**
* @param string $name
*/
public function getField($name)
{
if (!$this->hasField($name)) {
throw new \InvalidArgumentException(sprintf('Field "%s" does not exist.', $name));
}
return $this->fields[$name];
}
/**
* @param string $name
*/
public function hasField($name)
{
return array_key_exists($name, $this->fields);
}
/**
* @return array
*/
public function getActionGroups()
{
return $this->actionGroups;
}
/**
* @param ActionGroup $actionGroup
*/
public function addActionGroup(ActionGroup $actionGroup)
{
if ($this->hasActionGroup($name = $actionGroup->getName())) {
throw new \InvalidArgumentException(sprintf('ActionGroup "%s" already exists.', $name));
}
$this->actionGroups[$name] = $actionGroup;
}
/**
* @param string $name
*/
public function getActionGroup($name)
{
if (!$this->hasActionGroup($name)) {
throw new \InvalidArgumentException(sprintf('ActionGroup "%s" does not exist.', $name));
}
return $this->actionGroups[$name];
}
/**
* @param string $name
*/
public function hasActionGroup($name)
{
return array_key_exists($name, $this->actionGroups);
}
/**
* @return array
*/
public function getFilters()
{
return $this->filters;
}
/**
* @param Filter $filter
*/
public function addFilter(Filter $filter)
{
if ($this->hasFilter($name = $filter->getName())) {
throw new \InvalidArgumentException(sprintf('Filter "%s" already exists.', $name));
}
$this->filters[$name] = $filter;
}
/**
* @param string $name
*
* @return Filter
*/
public function getFilter($name)
{
if (!$this->hasFilter($name)) {
throw new \InvalidArgumentException(sprintf('Filter "%s" does not exist.', $name));
}
return $this->filters[$name];
}
/**
* @param string $name
*/
public function hasFilter($name)
{
return array_key_exists($name, $this->filters);
}
}

View file

@ -0,0 +1,103 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Filter;
use Sylius\Component\Grid\Data\DataSourceInterface;
use Sylius\Component\Grid\Data\ExpressionBuilderInterface;
use Sylius\Component\Grid\Filtering\FilterInterface;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class StringFilter implements FilterInterface
{
const TYPE_EQUAL = 'equal';
const TYPE_EMPTY = 'empty';
const TYPE_NOT_EMPTY = 'not_empty';
const TYPE_CONTAINS = 'contains';
const TYPE_NOT_CONTAINS = 'not_contains';
const TYPE_STARTS_WITH = 'starts_with';
const TYPE_ENDS_WITH = 'ends_with';
const TYPE_IN = 'in';
const TYPE_NOT_IN = 'not_in';
/**
* {@inheritdoc}
*/
public function apply(DataSourceInterface $dataSource, $name, $data, array $options)
{
$expressionBuilder = $dataSource->getExpressionBuilder();
if (!is_array($data)) {
$data = array('type' => self::TYPE_CONTAINS, 'value' => $data);
}
$fields = array_key_exists('fields', $options) ? $options['fields'] : array($name);
$type = $data['type'];
$value = array_key_exists('value', $data) ? $data['value'] : null;
if (1 === count($fields)) {
$expression = $this->getExpression($expressionBuilder, $type, $name, $value);
} else {
$expressions = array();
foreach ($fields as $field) {
$expressions[] = $this->getExpression($expressionBuilder, $type, $field, $value);
}
$expression = $expressionBuilder->orX($expressions);
}
$dataSource->restrict($expression);
}
/**
* Get expression.
*
* @param string $type
* @param string $field
* @param mixed $value
*/
private function getExpression(ExpressionBuilderInterface $expressionBuilder, $type, $field, $value)
{
switch ($type) {
case self::TYPE_EQUAL:
return $expressionBuilder->equals($field, $value);
break;
case self::TYPE_EMPTY:
return $expressionBuilder->isNull($field);
break;
case self::TYPE_NOT_EMPTY:
return $expressionBuilder->isNotNull($field);
break;
case self::TYPE_CONTAINS:
return $expressionBuilder->like($field, '%'.$value.'%');
break;
case self::TYPE_NOT_CONTAINS:
return $expressionBuilder->notLike($field, '%'.$value.'%');
break;
case self::TYPE_STARTS_WITH:
return $expressionBuilder->like($field, $value.'%');
break;
case self::TYPE_ENDS_WITH:
return $expressionBuilder->like($field, '%'.$value);
break;
case self::TYPE_IN:
return $expressionBuilder->in($field, array_map('trim', explode(',', $value)));
break;
case self::TYPE_NOT_IN:
return $expressionBuilder->notIn($field, array_map('trim', explode(',', $value)));
break;
}
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Filtering;
use Sylius\Component\Grid\Data\DataSourceInterface;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
interface FilterInterface
{
/**
* @param DataSourceInterface $dataSource
* @param string $name
* @param mixed $data
* @param array $options
*/
public function apply(DataSourceInterface $dataSource, $name, $data, array $options);
}

View file

@ -0,0 +1,58 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Filtering;
use Sylius\Component\Grid\Data\DataSourceInterface;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
use Sylius\Component\Registry\ServiceRegistryInterface;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class FiltersApplicator implements FiltersApplicatorInterface
{
/**
* @var ServiceRegistryInterface
*/
private $filtersRegistry;
/**
* @param ServiceRegistryInterface $filtersRegistry
*/
public function __construct(ServiceRegistryInterface $filtersRegistry)
{
$this->filtersRegistry = $filtersRegistry;
}
/**
* {@inheritdoc}
*/
public function apply(DataSourceInterface $dataSource, Grid $grid, Parameters $parameters)
{
if (!$parameters->has('criteria')) {
return;
}
$criteria = $parameters->get('criteria');
foreach ($criteria as $name => $data) {
if (!$grid->hasFilter($name)) {
continue;
}
$filter = $grid->getFilter($name);
$this->filtersRegistry->get($filter->getType())->apply($dataSource, $name, $data, $filter->getOptions());
}
}
}

View file

@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Filtering;
use Sylius\Component\Grid\Data\DataSourceInterface;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
interface FiltersApplicatorInterface
{
/**
* @param DataSourceInterface $dataSource
* @param Grid $grid
* @param Parameters $parameters
*/
public function apply(DataSourceInterface $dataSource, Grid $grid, Parameters $parameters);
}

View file

@ -0,0 +1,19 @@
Copyright (c) 2011-2016 Paweł Jędrzejewski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class Parameters
{
/**
* @var array
*/
private $parameters;
/**
* @param array $parameters
*/
public function __construct(array $parameters = array())
{
$this->parameters = $parameters;
}
/**
* @return array
*/
public function all()
{
return $this->parameters;
}
/**
* @return array
*/
public function keys()
{
return array_keys($this->parameters);
}
/**
* @param string $key
* @param mixed $default
*
* @return mixed
*/
public function get($key, $default = null)
{
return $this->has($key) ? $this->parameters[$key] : $default;
}
/**
* @param string $key
*
* @return Boolean
*/
public function has($key)
{
return array_key_exists($key, $this->parameters);
}
}

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Provider;
use Sylius\Component\Grid\Definition\ArrayToDefinitionConverterInterface;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class ArrayGridProvider implements GridProviderInterface
{
/**
* @var Grid[]
*/
private $grids = array();
/**
* ArrayGridProvider constructor.
* @param ArrayToDefinitionConverterInterface $converter
* @param array $gridConfigurations
*/
public function __construct(ArrayToDefinitionConverterInterface $converter, array $gridConfigurations)
{
foreach ($gridConfigurations as $code => $gridConfiguration) {
$this->grids[$code] = $converter->convert($code, $gridConfiguration);
}
}
/**
* {@inheritdoc}
*/
public function get($code)
{
if (!array_key_exists($code, $this->grids)) {
throw new UndefinedGridException($code);
}
return $this->grids[$code];
}
}

View file

@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Provider;
use Sylius\Component\Grid\Definition\Grid;
/**
* @author Paweł Jędrzejewski <pawel@svaluelius.org>
*/
interface GridProviderInterface
{
/**
* @param string $code
*
* @return Grid
*
* @throws UndefinedGridException
*/
public function get($code);
}

View file

@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Provider;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class UndefinedGridException extends \InvalidArgumentException
{
/**
* @param string $code
*/
public function __construct($code)
{
parent::__construct(sprintf('Grid "%s" does not exist.', $code));
}
}

View file

@ -0,0 +1,47 @@
Sylius Grid Component [![Build status...](https://secure.travis-ci.org/Sylius/Grid.png?branch=master)](http://travis-ci.org/Sylius/Grid)
=====================
PHP library for building grids of objects. Supports different drivers with filtering, sorting and custom field types.
Sylius
------
Sylius is an Open Source eCommerce solution based on Symfony.
Documentation
-------------
Documentation is available on [**docs.sylius.org**](http://docs.sylius.org/en/latest/components/Grid/index.html).
Contributing
------------
All instructions for contributing to Sylius can be found in the [Contributing Guide](http://docs.sylius.org/en/latest/contributing/index.html).
Support
-------
If you have a question regarding the usage of this library, please ask on
[Stackoverflow](http://stackoverflow.com). You should use "sylius"
tag when posting and make sure to [browse existing questions](http://stackoverflow.com/questions/tagged/sylius).
Sylius on Twitter
-----------------
[Follow the official Sylius account on Twitter!](http://twitter.com/Sylius).
Bug Tracking
------------
If you find a bug, please refer to the [Reporting a Bug](http://docs.sylius.org/en/latest/contributing/code/bugs.html) guide.
MIT License
-----------
License can be found [here](https://github.com/Sylius/Resource/blob/master/LICENSE).
Authors
-------
The component was originally created by [Paweł Jędrzejewski](http://pjedrzejewski.com).
See the list of [contributors](https://github.com/Sylius/Resource/contributors).

View file

@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Sorting;
use Sylius\Component\Grid\Data\DataSourceInterface;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class Sorter implements SorterInterface
{
/**
* {@inheritdoc}
*/
public function sort(DataSourceInterface $dataSource, Grid $grid, Parameters $parameters)
{
$expressionBuilder = $dataSource->getExpressionBuilder();
$sorting = $parameters->has('sorting') ? $parameters->get('sorting') : $grid->getSorting();
foreach ($sorting as $field => $direction) {
$expressionBuilder->addOrderBy($field, $direction);
}
}
}

View file

@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\Sorting;
use Sylius\Component\Grid\Data\DataSourceInterface;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
interface SorterInterface
{
/**
* @param DataSourceInterface $dataSource
* @param Grid $grid
* @param Parameters $parameters
*/
public function sort(DataSourceInterface $dataSource, Grid $grid, Parameters $parameters);
}

View file

@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\View;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class GridView
{
/**
* @var mixed
*/
private $data;
/**
* @var Grid
*/
private $definition;
/**
* @var Parameters
*/
private $parameters;
/**
* @param mixed $data
* @param Grid $definition
* @param Parameters $parameters
*/
public function __construct($data, Grid $definition, Parameters $parameters)
{
$this->data = $data;
$this->definition = $definition;
$this->parameters = $parameters;
}
/**
* @return mixed
*/
public function getData()
{
return $this->data;
}
/**
* @return Grid
*/
public function getDefinition()
{
return $this->definition;
}
/**
* @return Parameters
*/
public function getParameters()
{
return $this->parameters;
}
}

View file

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\View;
use Sylius\Component\Grid\Data\DataProviderInterface;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class GridViewFactory implements GridViewFactoryInterface
{
/**
* @var DataProviderInterface
*/
private $dataProvider;
/**
* @param DataProviderInterface $dataProvider
*/
public function __construct(DataProviderInterface $dataProvider)
{
$this->dataProvider = $dataProvider;
}
/**
* {@inheritdoc}
*/
public function create(Grid $grid, Parameters $parameters)
{
return new GridView($this->dataProvider->getData($grid, $parameters), $grid, $parameters);
}
}

View file

@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Grid\View;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
interface GridViewFactoryInterface
{
/**
* @param Grid $grid
* @param Parameters $parameters
*
* @return GridView
*/
public function create(Grid $grid, Parameters $parameters);
}

View file

@ -0,0 +1,44 @@
{
"name": "sylius/grid",
"type": "library",
"description": "PHP library to handle building grid of objects with fields and filters support.",
"keywords": ["shop", "ecommerce", "resource", "api", "sylius", "doctrine", "grid", "admin"],
"homepage": "http://sylius.org",
"license": "MIT",
"authors": [
{
"name": "Paweł Jędrzejewski",
"homepage": "http://pjedrzejewski.com"
},
{
"name": "Sylius project",
"homepage": "http://sylius.org"
},
{
"name": "Community contributions",
"homepage": "http://github.com/Sylius/Sylius/contributors"
}
],
"require": {
"php": "^5.5.9|^7.0",
"sylius/resource": "0.17.*"
},
"require-dev": {
"phpspec/phpspec": "^2.4"
},
"config": {
"bin-dir": "bin"
},
"autoload": {
"psr-4": { "Sylius\\Component\\Grid\\": "" }
},
"autoload-dev": {
"psr-4": { "Sylius\\Component\\Grid\\spec\\": "spec/" }
},
"extra": {
"branch-alias": {
"dev-master": "0.17-dev"
}
}
}

View file

@ -0,0 +1,5 @@
suites:
main:
namespace: Sylius\Component\Grid
psr4_prefix: Sylius\Component\Grid
src_path: .

View file

@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\Data;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Component\Grid\Data\DataProvider;
use Sylius\Component\Grid\Data\DataProviderInterface;
use Sylius\Component\Grid\Data\DataSourceInterface;
use Sylius\Component\Grid\Data\DataSourceProviderInterface;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Filtering\FiltersApplicatorInterface;
use Sylius\Component\Grid\Parameters;
use Sylius\Component\Grid\Sorting\SorterInterface;
use Sylius\Component\Registry\ServiceRegistryInterface;
/**
* @mixin DataProvider
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class DataProviderSpec extends ObjectBehavior
{
function let(DataSourceProviderInterface $dataSourceProvider, FiltersApplicatorInterface $filtersApplicator, SorterInterface $sorter)
{
$this->beConstructedWith($dataSourceProvider, $filtersApplicator, $sorter);
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\Data\DataProvider');
}
function it_implements_grid_data_provider_interface()
{
$this->shouldImplement(DataProviderInterface::class);
}
function it_gets_data_from_the_data_source(
DataSourceProviderInterface $dataSourceProvider,
DataSourceInterface $dataSource,
FiltersApplicatorInterface $filtersApplicator,
SorterInterface $sorter,
Grid $grid,
Parameters $parameters
) {
$dataSourceProvider->getDataSource($grid, $parameters)->willReturn($dataSource);
$filtersApplicator->apply($dataSource, $grid, $parameters)->shouldBeCalled();
$sorter->sort($dataSource, $grid, $parameters)->shouldBeCalled();
$dataSource->getData($parameters)->willReturn(array('foo', 'bar'));
$this->getData($grid, $parameters)->shouldReturn(array('foo', 'bar'));
}
}

View file

@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\Data;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Component\Grid\Data\DataSourceProvider;
use Sylius\Component\Grid\Data\DataSourceProviderInterface;
use Sylius\Component\Grid\Data\DriverInterface;
use Sylius\Component\Grid\Data\UnsupportedDriverException;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
use Sylius\Component\Registry\ServiceRegistryInterface;
/**
* @mixin DataSourceProvider
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class DataSourceProviderSpec extends ObjectBehavior
{
function let(ServiceRegistryInterface $driversRegistry)
{
$this->beConstructedWith($driversRegistry);
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\Data\DataSourceProvider');
}
function it_implements_grid_data_provider_interface()
{
$this->shouldImplement(DataSourceProviderInterface::class);
}
function it_uses_a_correct_driver_to_get_the_data_for_a_grid(
ServiceRegistryInterface $driversRegistry,
DriverInterface $driver,
Grid $grid,
Parameters $parameters
) {
$grid->getDriver()->willReturn('doctrine/orm');
$grid->getDriverConfiguration()->willReturn(array('resource' => 'sylius.tax_category'));
$driversRegistry->has('doctrine/orm')->willReturn(true);
$driversRegistry->get('doctrine/orm')->willReturn($driver);
$driver->getDataSource(array('resource' => 'sylius.tax_category'), $parameters)->willReturn(array('foo', 'bar'));
$this->getDataSource($grid, $parameters)->shouldReturn(array('foo', 'bar'));
}
function it_throws_an_exception_if_driver_is_not_supported(Grid $grid, Parameters $parameters, ServiceRegistryInterface $driversRegistry)
{
$grid->getDriver()->willReturn('doctrine/banana');
$driversRegistry->has('doctrine/banana')->willReturn(false);
$this
->shouldThrow(new UnsupportedDriverException('doctrine/banana'))
->during('getDataSource', array($grid, $parameters))
;
}
}

View file

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\Definition;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Component\Grid\Definition\ActionGroup;
use Sylius\Component\Grid\Definition\Action;
/**
* @mixin ActionGroup
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class ActionGroupSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedThrough('named', array('row'));
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\Definition\ActionGroup');
}
function it_has_code()
{
$this->getName()->shouldReturn('row');
}
function it_does_not_have_any_actions_by_default()
{
$this->getActions()->shouldReturn(array());
}
function it_can_have_action_definitions(Action $action)
{
$action->getName()->willReturn('display_summary');
$this->addAction($action);
$this->getAction('display_summary')->shouldReturn($action);
$this->getActions()->shouldReturn(array('display_summary' => $action));
}
function it_cannot_have_two_actions_with_the_same_name(Action $firstAction, Action $secondAction)
{
$firstAction->getName()->willReturn('read_book');
$secondAction->getName()->willReturn('read_book');
$this->addAction($firstAction);
$this
->shouldThrow(\InvalidArgumentException::class)
->during('addAction', array($secondAction))
;
}
function it_knows_if_action_with_given_name_already_exists(Action $action)
{
$action->getName()->willReturn('read_book');
$this->addAction($action);
$this->hasAction('read_book')->shouldReturn(true);
$this->hasAction('delete_book')->shouldReturn(false);
}
}

View file

@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\Definition;
use Sylius\Component\Grid\Definition\Action;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
/**
* @mixin Action
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class ActionSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedThrough('fromNameAndType', array('view', 'link'));
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\Definition\Action');
}
function it_has_name()
{
$this->getName()->shouldReturn('view');
}
function it_has_type()
{
$this->getType()->shouldReturn('link');
}
function it_has_label_which_defaults_to_name()
{
$this->getLabel()->shouldReturn('view');
$this->setLabel('Read book');
$this->getLabel()->shouldReturn('Read book');
}
}

View file

@ -0,0 +1,108 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\Definition;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Component\Grid\Definition\Action;
use Sylius\Component\Grid\Definition\ActionGroup;
use Sylius\Component\Grid\Definition\ArrayToDefinitionConverter;
use Sylius\Component\Grid\Definition\ArrayToDefinitionConverterInterface;
use Sylius\Component\Grid\Definition\Field;
use Sylius\Component\Grid\Definition\Filter;
use Sylius\Component\Grid\Definition\Grid;
/**
* @mixin ArrayToDefinitionConverter
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class ArrayToDefinitionConverterSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\Definition\ArrayToDefinitionConverter');
}
function it_implements_array_to_definition_converter()
{
$this->shouldImplement(ArrayToDefinitionConverterInterface::class);
}
function it_converts_an_array_to_grid_definition()
{
$grid = Grid::fromCodeAndDriverConfiguration('sylius_admin_tax_category', 'doctrine/orm', array('resource' => 'sylius.tax_category'));
$grid->setSorting(array('name' => 'desc'));
$codeField = Field::fromNameAndType('code', 'string');
$codeField->setLabel('System Code');
$codeField->setPath('method.code');
$grid->addField($codeField);
$viewAction = Action::fromNameAndType('view', 'link');
$viewAction->setLabel('Display Tax Category');
$defaultActionGroup = ActionGroup::named('default');
$defaultActionGroup->addAction($viewAction);
$grid->addActionGroup($defaultActionGroup);
$filter = Filter::fromNameAndType('enabled', 'boolean');
$grid->addFilter($filter);
$definitionArray = array(
'driver' => array(
'name' => 'doctrine/orm',
'options' => array('resource' => 'sylius.tax_category'),
),
'sorting' => array(
'name' => 'desc',
),
'fields' => array(
'code' => array(
'type' => 'string',
'label' => 'System Code',
'path' => 'method.code',
)
),
'filters' => array(
'enabled' => array(
'type' => 'boolean',
)
),
'actions' => array(
'default' => array(
'view' => array(
'type' => 'link',
'label' => 'Display Tax Category',
)
)
)
);
$this->convert('sylius_admin_tax_category', $definitionArray)->shouldBeSameGridAs($grid);
}
public function getMatchers()
{
return [
'beSameGridAs' => function ($subject, $key) {
if (!$subject instanceof Grid || !$key instanceof Grid) {
return false;
}
return serialize($subject) === serialize($key);
},
];
}
}

View file

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\Definition;
use Sylius\Component\Grid\Definition\Field;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
/**
* @mixin Field
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class FieldSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedThrough('fromNameAndType', array('enabled', 'boolean'));
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\Definition\Field');
}
function it_has_name()
{
$this->getName()->shouldReturn('enabled');
}
function it_has_type()
{
$this->getType()->shouldReturn('boolean');
}
function it_has_path_which_defaults_to_name()
{
$this->getPath()->shouldReturn('enabled');
$this->setPath('method.enabled');
$this->getPath()->shouldReturn('method.enabled');
}
function it_has_label_which_defaults_to_name()
{
$this->getLabel()->shouldReturn('enabled');
$this->setLabel('Is enabled?');
$this->getLabel()->shouldReturn('Is enabled?');
}
function it_has_no_options_by_default()
{
$this->getOptions()->shouldReturn(array());
}
function it_can_have_options()
{
$this->setOptions(array('template' => 'SyliusUiBundle:Grid/Field:_status.html.twig'));
$this->getOptions()->shouldReturn(array('template' => 'SyliusUiBundle:Grid/Field:_status.html.twig'));
}
}

View file

@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\Definition;
use Sylius\Component\Grid\Definition\Filter;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
/**
* @mixin Filter
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class FilterSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedThrough('fromNameAndType', array('keywords', 'string'));
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\Definition\Filter');
}
function it_has_name()
{
$this->getName()->shouldReturn('keywords');
}
function it_has_type()
{
$this->getType()->shouldReturn('string');
}
function it_has_label_which_defaults_to_name()
{
$this->getLabel()->shouldReturn('keywords');
$this->setLabel('Search by keyword');
$this->getLabel()->shouldReturn('Search by keyword');
}
function it_has_no_options_by_default()
{
$this->getOptions()->shouldReturn(array());
}
function it_can_have_options()
{
$this->setOptions(array('fields' => ['firstName', 'lastName', 'email']));
$this->getOptions()->shouldReturn(array('fields' => ['firstName', 'lastName', 'email']));
}
}

View file

@ -0,0 +1,176 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\Definition;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Component\Grid\Definition\ActionGroup;
use Sylius\Component\Grid\Definition\Field;
use Sylius\Component\Grid\Definition\Filter;
use Sylius\Component\Grid\Definition\Grid;
/**
* @mixin Grid
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class GridSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedThrough('fromCodeAndDriverConfiguration', array('sylius_admin_tax_category', 'doctrine/orm', array(
'resource' => 'sylius.tax_category',
'method' => 'createByCodeQueryBuilder',
'arguments' => ['$code']
)));
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\Definition\Grid');
}
function it_has_code()
{
$this->getCode()->shouldReturn('sylius_admin_tax_category');
}
function it_has_driver()
{
$this->getDriver()->shouldReturn('doctrine/orm');
}
function it_has_driver_configuration()
{
$this->getDriverConfiguration()->shouldReturn(array(
'resource' => 'sylius.tax_category',
'method' => 'createByCodeQueryBuilder',
'arguments' => ['$code']
));
}
function it_has_empty_sorting_configuration_by_default()
{
$this->getSorting()->shouldReturn(array());
}
function it_can_have_sorting_configuration()
{
$this->setSorting(array('name' => 'desc'));
$this->getSorting()->shouldReturn(array('name' => 'desc'));
}
function it_does_not_have_any_fields_by_default()
{
$this->getFields()->shouldReturn(array());
}
function it_can_have_field_definitions(Field $field)
{
$field->getName()->willReturn('description');
$this->addField($field);
$this->getField('description')->shouldReturn($field);
}
function it_cannot_have_two_fields_with_the_same_name(Field $firstField, Field $secondField)
{
$firstField->getName()->willReturn('created_at');
$secondField->getName()->willReturn('created_at');
$this->addField($firstField);
$this
->shouldThrow(\InvalidArgumentException::class)
->during('addField', array($secondField))
;
}
function it_knows_if_field_with_given_name_already_exists(Field $field)
{
$field->getName()->willReturn('enabled');
$this->addField($field);
$this->hasField('enabled')->shouldReturn(true);
$this->hasField('parent')->shouldReturn(false);
}
function it_does_not_have_any_action_groups_by_default()
{
$this->getActionGroups()->shouldReturn(array());
}
function it_can_have_action_group_definitions(ActionGroup $actionGroup)
{
$actionGroup->getName()->willReturn('default');
$this->addActionGroup($actionGroup);
$this->getActionGroup('default')->shouldReturn($actionGroup);
}
function it_cannot_have_two_action_groups_with_the_same_name(ActionGroup $firstActionGroup, ActionGroup $secondActionGroup)
{
$firstActionGroup->getName()->willReturn('row');
$secondActionGroup->getName()->willReturn('row');
$this->addActionGroup($firstActionGroup);
$this
->shouldThrow(\InvalidArgumentException::class)
->during('addActionGroup', array($secondActionGroup))
;
}
function it_knows_if_action_group_with_given_name_already_exists(ActionGroup $actionGroup)
{
$actionGroup->getName()->willReturn('row');
$this->addActionGroup($actionGroup);
$this->hasActionGroup('row')->shouldReturn(true);
$this->hasActionGroup('default')->shouldReturn(false);
}
function it_does_not_have_any_filters_by_default()
{
$this->getFilters()->shouldReturn(array());
}
function it_can_have_filter_definitions(Filter $filter)
{
$filter->getName()->willReturn('enabled');
$this->addFilter($filter);
$this->getFilter('enabled')->shouldReturn($filter);
}
function it_cannot_have_two_filters_with_the_same_name(Filter $firstFilter, Filter $secondFilter)
{
$firstFilter->getName()->willReturn('created_at');
$secondFilter->getName()->willReturn('created_at');
$this->addFilter($firstFilter);
$this
->shouldThrow(\InvalidArgumentException::class)
->during('addFilter', array($secondFilter))
;
}
function it_knows_if_filter_with_given_name_already_exists(Filter $filter)
{
$filter->getName()->willReturn('enabled');
$this->addFilter($filter);
$this->hasFilter('enabled')->shouldReturn(true);
$this->hasFilter('created_at')->shouldReturn(false);
}
}

View file

@ -0,0 +1,174 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\Filter;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Component\Grid\Data\DataSourceInterface;
use Sylius\Component\Grid\Data\ExpressionBuilderInterface;
use Sylius\Component\Grid\Filter\StringFilter;
use Sylius\Component\Grid\Filtering\FilterInterface;
/**
* @mixin StringFilter
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class StringFilterSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\Filter\StringFilter');
}
function it_implements_filter_interface()
{
$this->shouldImplement(FilterInterface::class);
}
function it_filters_with_like_by_default(
DataSourceInterface $dataSource,
ExpressionBuilderInterface $expressionBuilder
) {
$dataSource->getExpressionBuilder()->willReturn($expressionBuilder);
$expressionBuilder->like('firstName', '%John%')->willReturn('EXPR');
$dataSource->restrict('EXPR')->shouldBeCalled();
$this->apply($dataSource, 'firstName', 'John', array());
}
function it_filters_equal_strings(
DataSourceInterface $dataSource,
ExpressionBuilderInterface $expressionBuilder
) {
$dataSource->getExpressionBuilder()->willReturn($expressionBuilder);
$expressionBuilder->equals('firstName', 'John')->willReturn('EXPR');
$dataSource->restrict('EXPR')->shouldBeCalled();
$this->apply($dataSource, 'firstName', array('type' => StringFilter::TYPE_EQUAL, 'value' => 'John'), array());
}
function it_filters_data_containing_empty_strings(
DataSourceInterface $dataSource,
ExpressionBuilderInterface $expressionBuilder
)
{
$dataSource->getExpressionBuilder()->willReturn($expressionBuilder);
$expressionBuilder->isNull('firstName')->willReturn('EXPR');
$dataSource->restrict('EXPR')->shouldBeCalled();
$this->apply($dataSource, 'firstName', array('type' => StringFilter::TYPE_EMPTY), array());
}
function it_filters_data_containing_not_empty_strings(
DataSourceInterface $dataSource,
ExpressionBuilderInterface $expressionBuilder
) {
$dataSource->getExpressionBuilder()->willReturn($expressionBuilder);
$expressionBuilder->isNotNull('firstName')->willReturn('EXPR');
$dataSource->restrict('EXPR')->shouldBeCalled();
$this->apply($dataSource, 'firstName', array('type' => StringFilter::TYPE_NOT_EMPTY), array());
}
function it_filters_data_containing_a_string(
DataSourceInterface $dataSource,
ExpressionBuilderInterface $expressionBuilder
)
{
$dataSource->getExpressionBuilder()->willReturn($expressionBuilder);
$expressionBuilder->like('firstName', '%John%')->willReturn('EXPR');
$dataSource->restrict('EXPR')->shouldBeCalled();
$this->apply($dataSource, 'firstName', array('type' => StringFilter::TYPE_CONTAINS, 'value' => 'John'), array());
}
function it_filters_data_not_containing_a_string(
DataSourceInterface $dataSource,
ExpressionBuilderInterface $expressionBuilder
) {
$dataSource->getExpressionBuilder()->willReturn($expressionBuilder);
$expressionBuilder->notLike('firstName', '%John%')->willReturn('EXPR');
$dataSource->restrict('EXPR')->shouldBeCalled();
$this->apply($dataSource, 'firstName', array('type' => StringFilter::TYPE_NOT_CONTAINS, 'value' => 'John'), array());
}
function it_filters_data_starting_with_a_string(
DataSourceInterface $dataSource,
ExpressionBuilderInterface $expressionBuilder
) {
$dataSource->getExpressionBuilder()->willReturn($expressionBuilder);
$expressionBuilder->like('firstName', 'John%')->willReturn('EXPR');
$dataSource->restrict('EXPR')->shouldBeCalled();
$this->apply($dataSource, 'firstName', array('type' => StringFilter::TYPE_STARTS_WITH, 'value' => 'John'), array());
}
function it_filters_data_ending_with_a_string(
DataSourceInterface $dataSource,
ExpressionBuilderInterface $expressionBuilder
) {
$dataSource->getExpressionBuilder()->willReturn($expressionBuilder);
$expressionBuilder->like('firstName', '%John')->willReturn('EXPR');
$dataSource->restrict('EXPR')->shouldBeCalled();
$this->apply($dataSource, 'firstName', array('type' => StringFilter::TYPE_ENDS_WITH, 'value' => 'John'), array());
}
function it_filters_data_containing_one_of_strings(
DataSourceInterface $dataSource,
ExpressionBuilderInterface $expressionBuilder
) {
$dataSource->getExpressionBuilder()->willReturn($expressionBuilder);
$expressionBuilder->in('firstName', array('John', 'Paul', 'Rick'))->willReturn('EXPR');
$dataSource->restrict('EXPR')->shouldBeCalled();
$this->apply($dataSource, 'firstName', array('type' => StringFilter::TYPE_IN, 'value' => 'John, Paul,Rick'), array());
}
function it_filters_data_containing_none_of_strings(
DataSourceInterface $dataSource,
ExpressionBuilderInterface $expressionBuilder
) {
$dataSource->getExpressionBuilder()->willReturn($expressionBuilder);
$expressionBuilder->notIn('firstName', array('John', 'Paul', 'Rick'))->willReturn('EXPR');
$dataSource->restrict('EXPR')->shouldBeCalled();
$this->apply($dataSource, 'firstName', array('type' => StringFilter::TYPE_NOT_IN, 'value' => 'John, Paul,Rick'), array());
}
function it_filters_in_multiple_fields(
DataSourceInterface $dataSource,
ExpressionBuilderInterface $expressionBuilder
) {
$dataSource->getExpressionBuilder()->willReturn($expressionBuilder);
$expressionBuilder->like('firstName', '%John%')->willReturn('EXPR1');
$expressionBuilder->like('lastName', '%John%')->willReturn('EXPR2');
$expressionBuilder->orX(array('EXPR1', 'EXPR2'))->willReturn('EXPR');
$dataSource->restrict('EXPR')->shouldBeCalled();
$this->apply($dataSource, 'name', 'John', array('fields' => ['firstName', 'lastName']));
}
}

View file

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\Filtering;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Component\Grid\Data\DataSourceInterface;
use Sylius\Component\Grid\Definition\Filter;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Filtering\FilterInterface;
use Sylius\Component\Grid\Filtering\FiltersApplicator;
use Sylius\Component\Grid\Filtering\FiltersApplicatorInterface;
use Sylius\Component\Grid\Parameters;
use Sylius\Component\Registry\ServiceRegistryInterface;
/**
* @mixin FiltersApplicator
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class FiltersApplicatorSpec extends ObjectBehavior
{
function let(ServiceRegistryInterface $filtersRegistry)
{
$this->beConstructedWith($filtersRegistry);
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\Filtering\FiltersApplicator');
}
function it_implements_filters_applicator_interface()
{
$this->shouldImplement(FiltersApplicatorInterface::class);
}
function it_filters_data_source_based_on_criteria_parameter(
ServiceRegistryInterface $filtersRegistry,
FilterInterface $stringFilter,
Grid $grid,
Filter $filter,
Parameters $parameters,
DataSourceInterface $dataSource
) {
$parameters->has('criteria')->willReturn(true);
$parameters->get('criteria')->willReturn(array('keywords' => 'Banana', 'enabled' => true));
$grid->hasFilter('keywords')->willReturn(true);
$grid->hasFilter('enabled')->willReturn(false);
$grid->getFilter('keywords')->willReturn($filter);
$filter->getType()->willReturn('string');
$filter->getOptions()->willReturn(array('fields' => ['firstName', 'lastName']));
$filtersRegistry->get('string')->willReturn($stringFilter);
$stringFilter->apply($dataSource, 'keywords', 'Banana', array('fields' => ['firstName', 'lastName']))->shouldBeCalled();
$this->apply($dataSource, $grid, $parameters);
}
}

View file

@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\Provider;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Component\Grid\Definition\ArrayToDefinitionConverterInterface;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Provider\ArrayGridProvider;
use Sylius\Component\Grid\Provider\GridProviderInterface;
use Sylius\Component\Grid\Provider\UndefinedGridException;
/**
* @mixin ArrayGridProvider
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class ArrayGridProviderSpec extends ObjectBehavior
{
function let(ArrayToDefinitionConverterInterface $converter, Grid $firstGrid, Grid $secondGrid, Grid $thirdGrid)
{
$converter->convert('sylius_admin_tax_category', array('configuration1'))->willReturn($firstGrid);
$converter->convert('sylius_admin_product', array('configuration2'))->willReturn($secondGrid);
$converter->convert('sylius_admin_order', array('configuration3'))->willReturn($thirdGrid);
$this->beConstructedWith($converter, array(
'sylius_admin_tax_category' => array('configuration1'),
'sylius_admin_product' => array('configuration2'),
'sylius_admin_order' => array('configuration3'),
));
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\Provider\ArrayGridProvider');
}
function it_implements_grid_provider_interface()
{
$this->shouldImplement(GridProviderInterface::class);
}
function it_returns_grid_definition_by_name(Grid $firstGrid, Grid $secondGrid, Grid $thirdGrid)
{
$this->get('sylius_admin_tax_category')->shouldReturn($firstGrid);
$this->get('sylius_admin_product')->shouldReturn($secondGrid);
$this->get('sylius_admin_order')->shouldReturn($thirdGrid);
}
function it_throws_an_exception_if_grid_does_not_exist()
{
$this
->shouldThrow(new UndefinedGridException('sylius_admin_order_item'))
->during('get', array('sylius_admin_order_item'))
;
}
}

View file

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\Sorting;
use Sylius\Component\Grid\Data\DataSourceInterface;
use Sylius\Component\Grid\Data\ExpressionBuilderInterface;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
use Sylius\Component\Grid\Sorting\Sorter;
use Sylius\Component\Grid\Sorting\SorterInterface;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
/**
* @mixin Sorter
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class SorterSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\Sorting\Sorter');
}
function it_implements_grid_data_source_sorter_interface()
{
$this->shouldImplement(SorterInterface::class);
}
function it_sorts_the_data_source_via_expression_builder_based_on_the_grid_definition(
Grid $grid,
Parameters $parameters,
DataSourceInterface $dataSource,
ExpressionBuilderInterface $expressionBuilder
) {
$parameters->has('sorting')->willReturn(false);
$dataSource->getExpressionBuilder()->willReturn($expressionBuilder);
$grid->getSorting()->willReturn(array('name' => 'desc'));
$expressionBuilder->addOrderBy('name', 'desc')->shouldBeCalled();
$this->sort($dataSource, $grid, $parameters);
}
function it_sorts_the_data_source_via_expression_builder_based_on_sorting_parameter(
Grid $grid,
Parameters $parameters,
DataSourceInterface $dataSource,
ExpressionBuilderInterface $expressionBuilder
) {
$parameters->has('sorting')->willReturn(true);
$parameters->get('sorting')->willReturn(array('name' => 'asc'));
$dataSource->getExpressionBuilder()->willReturn($expressionBuilder);
$grid->getSorting()->willReturn(array('name' => 'desc'));
$expressionBuilder->addOrderBy('name', 'asc')->shouldBeCalled();
$this->sort($dataSource, $grid, $parameters);
}
}

View file

@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\View;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Component\Grid\Data\DataProviderInterface;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
use Sylius\Component\Grid\View\GridView;
use Sylius\Component\Grid\View\GridViewFactory;
use Sylius\Component\Grid\View\GridViewFactoryInterface;
/**
* @mixin GridViewFactory
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class GridViewFactorySpec extends ObjectBehavior
{
function let(DataProviderInterface $dataProvider)
{
$this->beConstructedWith($dataProvider);
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\View\GridViewFactory');
}
function it_implements_grid_view_factory_interface()
{
$this->shouldImplement(GridViewFactoryInterface::class);
}
function it_uses_data_provider_to_create_a_view_with_data_and_definition(
DataProviderInterface $dataProvider,
Grid $grid,
Parameters $parameters
) {
$expectedGridView = new GridView(array('foo', 'bar'), $grid->getWrappedObject(), $parameters->getWrappedObject());
$dataProvider->getData($grid, $parameters)->willReturn(array('foo', 'bar'));
$this->create($grid, $parameters)->shouldBeSameGridViewAs($expectedGridView);
}
public function getMatchers()
{
return [
'beSameGridViewAs' => function ($subject, $key) {
if (!$subject instanceof GridView || !$key instanceof GridView) {
return false;
}
return serialize($subject) === serialize($key);
},
];
}
}

View file

@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Grid\View;
use Sylius\Component\Grid\Definition\Grid;
use Sylius\Component\Grid\Parameters;
use Sylius\Component\Grid\View\GridView;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
/**
* @mixin GridView
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
*/
class GridViewSpec extends ObjectBehavior
{
function let(Grid $gridDefinition, Parameters $parameters)
{
$this->beConstructedWith(array('foo', 'bar'), $gridDefinition, $parameters);
}
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Grid\View\GridView');
}
function it_has_data()
{
$this->getData()->shouldReturn(array('foo', 'bar'));
}
function it_has_definition(Grid $gridDefinition)
{
$this->getDefinition()->shouldReturn($gridDefinition);
}
function it_has_parameters(Parameters $parameters)
{
$this->getParameters()->shouldReturn($parameters);
}
}