├── .gitignore ├── Builder ├── DatagridBuilderInterface.php └── Doctrine │ ├── AbstractDatagridBuilder.php │ ├── AbstractQueryBuilderFactory.php │ ├── Native │ ├── DatagridBuilder.php │ ├── DatagridContext.php │ └── QueryBuilderFactory.php │ └── ORM │ ├── DatagridBuilder.php │ ├── DatagridContext.php │ └── QueryBuilderFactory.php ├── Datagrid ├── DatagridInterface.php ├── Doctrine │ ├── AbstractDatagrid.php │ ├── Native │ │ └── Datagrid.php │ ├── NativeSqlDatagrid.php │ └── ORM │ │ └── Datagrid.php └── EmptyDatagrid.php ├── DependencyInjection ├── Configuration.php └── GlavwebDatagridExtension.php ├── Doctrine └── ORM │ └── Functions │ └── Cast.php ├── Exception ├── AccessDeniedException.php ├── BuildException.php └── Exception.php ├── Factory ├── DatagridFactoryInterface.php ├── NativeDatagridFactory.php └── ORMDatagridFactory.php ├── Filter ├── Doctrine │ ├── AbstractFilter.php │ ├── AbstractFilterFactory.php │ ├── FilterTypeGuesser.php │ ├── Native │ │ ├── AbstractFilter.php │ │ ├── BooleanFilter.php │ │ ├── CallbackFilter.php │ │ ├── DateTimeFilter.php │ │ ├── EnumFilter.php │ │ ├── FilterFactory.php │ │ ├── ModelFilter.php │ │ ├── NumberFilter.php │ │ └── StringFilter.php │ └── ORM │ │ ├── AbstractFilter.php │ │ ├── BooleanFilter.php │ │ ├── CallbackFilter.php │ │ ├── DateTimeFilter.php │ │ ├── EnumFilter.php │ │ ├── FilterFactory.php │ │ ├── ModelFilter.php │ │ ├── NumberFilter.php │ │ └── StringFilter.php ├── FilterInterface.php ├── FilterStack.php └── TypeGuess.php ├── GlavwebDatagridBundle.php ├── JoinMap └── Doctrine │ ├── JoinBuilderInterface.php │ ├── JoinMap.php │ ├── Native │ └── JoinBuilder.php │ └── ORM │ └── JoinBuilder.php ├── LICENSE ├── README.md ├── Resources └── config │ └── services.yml ├── ci └── integration-test │ ├── Dockerfile │ ├── docker-compose.yml │ ├── environment │ ├── .env.test │ ├── config │ │ ├── packages │ │ │ ├── doctrine.yaml │ │ │ └── glavweb_data_schema.yaml │ │ └── services.yaml │ ├── data_schemas │ │ ├── article │ │ │ └── simple_data.schema.yml │ │ ├── event │ │ │ └── simple_data.schema.yml │ │ ├── event_detail │ │ │ └── simple_data.schema.yml │ │ ├── simple_data.schema.yml │ │ ├── test_decode_with_query_selects.schema.yml │ │ ├── test_decode_with_query_selects_orm.schema.yml │ │ ├── test_embeds_first_level.schema.yml │ │ ├── test_embeds_second_level.schema.yml │ │ ├── test_joins_first_level.schema.yml │ │ ├── test_joins_second_level.schema.yml │ │ ├── test_load.schema.yml │ │ └── test_merged.schema.yml │ ├── phpunit.xml.dist │ ├── scopes │ │ ├── article │ │ │ ├── list.yml │ │ │ ├── short.yml │ │ │ └── view.yml │ │ ├── event │ │ │ └── short.yml │ │ └── event_detail │ │ │ └── short.yml │ ├── src │ │ ├── DataFixtures │ │ │ └── ORM │ │ │ │ ├── LoadArticleData.php │ │ │ │ ├── LoadEventData.php │ │ │ │ ├── LoadEventDetailData.php │ │ │ │ ├── LoadEventGroupData.php │ │ │ │ ├── LoadSessionData.php │ │ │ │ └── LoadTagData.php │ │ ├── DataSchema │ │ │ └── AppDataSchemaExtension.php │ │ └── Entity │ │ │ ├── Article.php │ │ │ ├── Event.php │ │ │ ├── EventDetail.php │ │ │ ├── EventGroup.php │ │ │ ├── Session.php │ │ │ └── Tag.php │ └── tests │ │ └── Datagrid │ │ └── Doctrine │ │ ├── DatagridNativeTest.php │ │ └── DatagridOrmTest.php │ ├── run.sh │ └── scripts │ ├── copy.sh │ └── run.sh ├── composer.json └── phpunit.xml.dist /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | build/ 3 | composer.lock 4 | vendor/* -------------------------------------------------------------------------------- /Builder/DatagridBuilderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Builder; 13 | 14 | use Glavweb\DatagridBundle\Datagrid\DatagridInterface; 15 | use Glavweb\DatagridBundle\Filter\FilterInterface; 16 | 17 | /** 18 | * Interface DatagridBuilderInterface 19 | * 20 | * @package Glavweb\DatagridBundle 21 | * @author Andrey Nilov 22 | */ 23 | interface DatagridBuilderInterface 24 | { 25 | /** 26 | * @param array $orderings 27 | * @return $this 28 | */ 29 | public function setOrderings($orderings); 30 | 31 | /** 32 | * @return array 33 | */ 34 | public function getOrderings(); 35 | 36 | /** 37 | * @param int $firstResult 38 | * 39 | * @return $this 40 | */ 41 | public function setFirstResult($firstResult); 42 | 43 | /** 44 | * @return int 45 | */ 46 | public function getFirstResult(); 47 | 48 | /** 49 | * @param int $maxResults 50 | * 51 | * @return $this 52 | */ 53 | public function setMaxResults($maxResults); 54 | 55 | /** 56 | * @return int 57 | */ 58 | public function getMaxResults(); 59 | 60 | /** 61 | * @param array $operators 62 | * @return $this 63 | */ 64 | public function setOperators(array $operators); 65 | 66 | /** 67 | * @return array 68 | */ 69 | public function getOperators(); 70 | 71 | /** 72 | * @param string $alias 73 | * 74 | * @return $this 75 | */ 76 | public function setAlias($alias); 77 | 78 | /** 79 | * @return string 80 | */ 81 | public function getAlias(); 82 | 83 | /** 84 | * @param FilterInterface[] $filters 85 | * 86 | * @return $this 87 | */ 88 | public function setFilters(array $filters = []); 89 | 90 | /** 91 | * @return FilterInterface[] 92 | */ 93 | public function getFilters(); 94 | 95 | /** 96 | * @param string $filterName 97 | * @return FilterInterface 98 | */ 99 | public function getFilter($filterName); 100 | 101 | /** 102 | * @param string $filterName 103 | * @param string $type 104 | * @param array $options 105 | * @return $this 106 | */ 107 | public function addFilter($filterName, $type = null, $options = []); 108 | 109 | /** 110 | * @param array $parameters 111 | * @param \Closure $callback 112 | * @return DatagridInterface 113 | */ 114 | public function build(array $parameters = [], $callback = null); 115 | } -------------------------------------------------------------------------------- /Builder/Doctrine/AbstractDatagridBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Builder\Doctrine; 13 | 14 | use Doctrine\Bundle\DoctrineBundle\Registry; 15 | use Glavweb\DatagridBundle\Builder\DatagridBuilderInterface; 16 | use Glavweb\DatagridBundle\Datagrid\DatagridInterface; 17 | use Glavweb\DatagridBundle\Exception\BuildException; 18 | use Glavweb\DataSchemaBundle\DataSchema\DataSchema; 19 | use Glavweb\DataSchemaBundle\DataSchema\DataSchemaFactory; 20 | use Glavweb\DatagridBundle\Filter\Doctrine\AbstractFilterFactory; 21 | use Glavweb\DatagridBundle\Filter\FilterInterface; 22 | use Glavweb\DatagridBundle\Filter\FilterStack; 23 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinMap; 24 | 25 | /** 26 | * Class AbstractDatagridBuilder 27 | * 28 | * @package Glavweb\DatagridBundle 29 | * @author Andrey Nilov 30 | */ 31 | abstract class AbstractDatagridBuilder implements DatagridBuilderInterface 32 | { 33 | /** 34 | * @var Registry 35 | */ 36 | protected $doctrine; 37 | 38 | /** 39 | * @var AbstractFilterFactory 40 | */ 41 | protected $filterFactory; 42 | 43 | /** 44 | * @var DataSchemaFactory 45 | */ 46 | protected $dataSchemaFactory; 47 | 48 | /** 49 | * @var AbstractQueryBuilderFactory 50 | */ 51 | protected $queryBuilderFactory; 52 | 53 | /** 54 | * @var FilterStack 55 | */ 56 | protected $filterStack; 57 | 58 | /** 59 | * @var array 60 | */ 61 | protected $orderings; 62 | 63 | /** 64 | * @var int 65 | */ 66 | protected $firstResult; 67 | 68 | /** 69 | * @var int 70 | */ 71 | protected $maxResults; 72 | 73 | /** 74 | * @var array 75 | */ 76 | protected $operators = []; 77 | 78 | /** 79 | * @var string 80 | */ 81 | protected $alias = 't'; 82 | 83 | /** 84 | * @var string 85 | */ 86 | protected $entityClassName; 87 | 88 | /** 89 | * @var JoinMap 90 | */ 91 | protected $joinMap; 92 | 93 | /** 94 | * @var DataSchema 95 | */ 96 | protected $dataSchema; 97 | 98 | /** 99 | * @param array $parameters 100 | * @param \Closure $callback 101 | * @return DatagridInterface 102 | * @throws BuildException 103 | */ 104 | abstract public function build(array $parameters = [], $callback = null); 105 | 106 | /** 107 | * DoctrineDatagridBuilder constructor. 108 | * 109 | * @param Registry $doctrine 110 | * @param AbstractFilterFactory $filterFactory 111 | * @param DataSchemaFactory $dataSchemaFactory 112 | * @param AbstractQueryBuilderFactory $queryBuilderFactory 113 | */ 114 | public function __construct(Registry $doctrine, 115 | AbstractFilterFactory $filterFactory, 116 | DataSchemaFactory $dataSchemaFactory, 117 | AbstractQueryBuilderFactory $queryBuilderFactory) 118 | { 119 | $this->doctrine = $doctrine; 120 | $this->filterFactory = $filterFactory; 121 | $this->dataSchemaFactory = $dataSchemaFactory; 122 | $this->queryBuilderFactory = $queryBuilderFactory; 123 | $this->filterStack = new FilterStack(); 124 | } 125 | 126 | /** 127 | * @return FilterStack 128 | */ 129 | public function getFilterStack() 130 | { 131 | return $this->filterStack; 132 | } 133 | 134 | /** 135 | * @param array $orderings 136 | * @return $this 137 | */ 138 | public function setOrderings($orderings) 139 | { 140 | $this->orderings = $orderings; 141 | 142 | return $this; 143 | } 144 | 145 | /** 146 | * @return array 147 | */ 148 | public function getOrderings() 149 | { 150 | return $this->orderings; 151 | } 152 | 153 | /** 154 | * @param int $firstResult 155 | * 156 | * @return $this 157 | */ 158 | public function setFirstResult($firstResult) 159 | { 160 | $this->firstResult = $firstResult; 161 | 162 | return $this; 163 | } 164 | 165 | /** 166 | * @return int 167 | */ 168 | public function getFirstResult() 169 | { 170 | return $this->firstResult; 171 | } 172 | 173 | /** 174 | * @param int $maxResults 175 | * 176 | * @return $this 177 | */ 178 | public function setMaxResults($maxResults) 179 | { 180 | $this->maxResults = $maxResults; 181 | 182 | return $this; 183 | } 184 | 185 | /** 186 | * @return int 187 | */ 188 | public function getMaxResults() 189 | { 190 | return $this->maxResults; 191 | } 192 | 193 | /** 194 | * @param array $operators 195 | * @return $this 196 | */ 197 | public function setOperators(array $operators) 198 | { 199 | $this->operators = $operators; 200 | 201 | return $this; 202 | } 203 | 204 | /** 205 | * @return array 206 | */ 207 | public function getOperators() 208 | { 209 | return $this->operators; 210 | } 211 | 212 | /** 213 | * @param string $alias 214 | * 215 | * @return $this 216 | */ 217 | public function setAlias($alias) 218 | { 219 | $this->alias = $alias; 220 | 221 | return $this; 222 | } 223 | 224 | /** 225 | * @return string 226 | */ 227 | public function getAlias() 228 | { 229 | return $this->alias; 230 | } 231 | 232 | /** 233 | * @return null|string 234 | * @throws BuildException 235 | */ 236 | public function getEntityClassName(): ?string 237 | { 238 | if (!$this->entityClassName) { 239 | if (!$this->dataSchema instanceof DataSchema) { 240 | throw new BuildException('The Data Schema is not defined.'); 241 | } 242 | 243 | $configuration = $this->dataSchema->getConfiguration(); 244 | $this->entityClassName = isset($configuration['class']) ? $configuration['class'] : null; 245 | } 246 | 247 | return $this->entityClassName; 248 | } 249 | 250 | /** 251 | * @param JoinMap $joinMap 252 | * 253 | * @return $this 254 | */ 255 | public function setJoinMap(JoinMap $joinMap) 256 | { 257 | if ($this->joinMap) { 258 | $this->joinMap->merge($joinMap); 259 | 260 | } else { 261 | $this->joinMap = $joinMap; 262 | } 263 | 264 | return $this; 265 | } 266 | 267 | /** 268 | * @return JoinMap|null 269 | */ 270 | public function getJoinMap() 271 | { 272 | return $this->joinMap; 273 | } 274 | 275 | /** 276 | * @param FilterInterface[] $filters 277 | * 278 | * @return $this 279 | */ 280 | public function setFilters(array $filters = []) 281 | { 282 | foreach ($filters as $filterName => $filterValue) { 283 | $this->addFilter($filterName, $filterValue); 284 | } 285 | 286 | return $this; 287 | } 288 | 289 | /** 290 | * @param string $filterName 291 | * @param string $type 292 | * @param array $options 293 | * @return $this 294 | */ 295 | public function addFilter($filterName, $type = null, $options = []) 296 | { 297 | $entityClass = $this->getEntityClassName(); 298 | $alias = $this->getAlias(); 299 | 300 | $filter = $this->filterFactory->createForEntity($entityClass, $alias, $filterName, $type, $options); 301 | $this->fixFilter($filter); 302 | 303 | $this->filterStack->add($filter); 304 | 305 | return $this; 306 | } 307 | 308 | /** 309 | * @param string $filterName 310 | * @return FilterInterface 311 | */ 312 | public function getFilter($filterName) 313 | { 314 | return $this->filterStack->get($filterName); 315 | } 316 | 317 | /** 318 | * @return FilterInterface[] 319 | */ 320 | public function getFilters() 321 | { 322 | return $this->filterStack->all(); 323 | } 324 | 325 | /** 326 | * @param string $dataSchemaFile 327 | * @param string $scopeFile 328 | * @return $this 329 | */ 330 | public function setDataSchema($dataSchemaFile, $scopeFile = null) 331 | { 332 | $dataSchema = $this->dataSchemaFactory->createDataSchema($dataSchemaFile, $scopeFile); 333 | $this->dataSchema = $dataSchema; 334 | 335 | return $this; 336 | } 337 | 338 | /** 339 | * @param string $propertyPath 340 | * @param string $conditionName 341 | * @return self 342 | */ 343 | public function enablePropertyCondition(string $propertyPath, string $conditionName): self 344 | { 345 | $this->dataSchema->enablePropertyCondition($propertyPath, $conditionName); 346 | 347 | return $this; 348 | } 349 | 350 | /** 351 | * @param string $propertyPath 352 | * @param string $conditionName 353 | * @return self 354 | */ 355 | public function disablePropertyCondition(string $propertyPath, string $conditionName): self 356 | { 357 | $this->dataSchema->disablePropertyCondition($propertyPath, $conditionName); 358 | 359 | return $this; 360 | } 361 | 362 | /** 363 | * @param string $propertyPath 364 | * @param string $orderByPropertyName 365 | * @param string $order 366 | * @return $this 367 | */ 368 | public function setPropertyOrderBy(string $propertyPath, string $orderByPropertyName, string $order): self 369 | { 370 | $this->dataSchema->setPropertyOrderBy($propertyPath, $orderByPropertyName, $order); 371 | 372 | return $this; 373 | } 374 | 375 | /** 376 | * @param FilterInterface $filter 377 | */ 378 | private function fixFilter(FilterInterface $filter) 379 | { 380 | $paramName = $filter->getParamName(); 381 | if (isset($this->operators[$paramName])) { 382 | $options = $filter->getOptions(); 383 | $options['operator'] = $this->operators[$paramName]; 384 | 385 | $filter->setOptions($options); 386 | } 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /Builder/Doctrine/AbstractQueryBuilderFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Builder\Doctrine; 13 | 14 | use Doctrine\Bundle\DoctrineBundle\Registry; 15 | use Doctrine\ORM\EntityManager; 16 | use Doctrine\ORM\Mapping\ClassMetadata; 17 | use Doctrine\ORM\QueryBuilder; 18 | use Doctrine\DBAL\Query\QueryBuilder as NativeQueryBuilder; 19 | use Glavweb\DataSchemaBundle\DataSchema\DataSchema; 20 | use Glavweb\DatagridBundle\Filter\FilterStack; 21 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinMap; 22 | use Glavweb\DataSchemaBundle\DataSchema\Placeholder; 23 | 24 | /** 25 | * Class AbstractQueryBuilderFactory 26 | * 27 | * @package Glavweb\DatagridBundle 28 | * @author Andrey Nilov 29 | */ 30 | abstract class AbstractQueryBuilderFactory 31 | { 32 | /** 33 | * @var Registry 34 | */ 35 | protected $doctrine; 36 | 37 | /** 38 | * @var Placeholder 39 | */ 40 | protected $placeholder; 41 | 42 | /** 43 | * @param array $parameters 44 | * @param string $alias 45 | * @param DataSchema $dataSchema 46 | * @param FilterStack $filterStack 47 | * @param JoinMap|null $joinMap 48 | * @return NativeQueryBuilder|QueryBuilder 49 | */ 50 | abstract public function create(array $parameters, string $alias, DataSchema $dataSchema, FilterStack $filterStack, JoinMap $joinMap = null); 51 | 52 | /** 53 | * QueryBuilderFactory constructor. 54 | * 55 | * @param Registry $doctrine 56 | * @param Placeholder $placeholder 57 | */ 58 | public function __construct(Registry $doctrine, Placeholder $placeholder) 59 | { 60 | $this->doctrine = $doctrine; 61 | $this->placeholder = $placeholder; 62 | } 63 | 64 | /** 65 | * @param array $config 66 | * @param string|null $discriminator 67 | * @return string 68 | */ 69 | protected function getClassNameByDataSchema(array $config, string $discriminator = null): string 70 | { 71 | $class = $config['class']; 72 | 73 | if ($discriminator && !isset($config['discriminatorMap'][$discriminator])) { 74 | var_dump($config, $discriminator); echo __CLASS__ . ': ' . __LINE__; exit; 75 | } 76 | 77 | if ($discriminator && isset($config['discriminatorMap'][$discriminator])) { 78 | $class = $config['discriminatorMap'][$discriminator]; 79 | } 80 | 81 | return $class; 82 | } 83 | 84 | /** 85 | * @param string $class 86 | * @return ClassMetadata 87 | */ 88 | protected function getClassMetadata(string $class): ClassMetadata 89 | { 90 | /** @var EntityManager $em */ 91 | $em = $this->doctrine->getManager(); 92 | 93 | return $em->getClassMetadata($class); 94 | } 95 | 96 | /** 97 | * @param array $config 98 | * @param string|null $discriminator 99 | * @return ClassMetadata 100 | */ 101 | protected function getClassMetadataByDataSchema(array $config, string $discriminator = null): ClassMetadata 102 | { 103 | return $this->getClassMetadata( 104 | $this->getClassNameByDataSchema($config, $discriminator) 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Builder/Doctrine/Native/DatagridBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Builder\Doctrine\Native; 13 | 14 | use Doctrine\DBAL\Query\QueryBuilder; 15 | use Doctrine\ORM\EntityManager; 16 | use Doctrine\ORM\Mapping\ClassMetadata; 17 | use Glavweb\DatagridBundle\Builder\DatagridBuilderInterface; 18 | use Glavweb\DatagridBundle\Builder\Doctrine\AbstractDatagridBuilder; 19 | use Glavweb\DatagridBundle\Datagrid\Doctrine\Native\Datagrid; 20 | use Glavweb\DatagridBundle\Datagrid\EmptyDatagrid; 21 | use Glavweb\DatagridBundle\Exception\BuildException; 22 | use Glavweb\DatagridBundle\Exception\Exception; 23 | use Glavweb\DataSchemaBundle\DataSchema\DataSchema; 24 | 25 | /** 26 | * Class Builder 27 | * 28 | * @package Glavweb\DatagridBundle 29 | * @author Andrey Nilov 30 | */ 31 | class DatagridBuilder extends AbstractDatagridBuilder implements DatagridBuilderInterface 32 | { 33 | /** 34 | * @param array $parameters 35 | * @param \Closure $callback 36 | * @return Datagrid 37 | * @throws BuildException 38 | */ 39 | public function build(array $parameters = [], $callback = null) 40 | { 41 | if ($this->doctrine->getConnection()->getDatabasePlatform()->getName() != 'postgresql') { 42 | throw new BuildException('The Native Datagrid support only PostgreSQL database.'); 43 | } 44 | 45 | if (!$this->dataSchema instanceof DataSchema) { 46 | throw new BuildException('The Data Schema is not defined.'); 47 | } 48 | 49 | $orderings = $this->getOrderings(); 50 | $firstResult = $this->getFirstResult(); 51 | $maxResults = $this->getMaxResults(); 52 | $alias = $this->getAlias(); 53 | 54 | try { 55 | /** @var EntityManager $em */ 56 | $em = $this->doctrine->getManager(); 57 | $queryBuilder = $this->createQueryBuilder($parameters); 58 | 59 | $datagridContext = new DatagridContext( 60 | $this->getEntityClassName(), 61 | $em, 62 | $queryBuilder, 63 | $this->filterStack, 64 | $this->dataSchema, 65 | (array)$orderings, 66 | (int)$firstResult, 67 | $maxResults, 68 | $alias, 69 | $parameters 70 | ); 71 | 72 | if (is_callable($callback)) { 73 | $callback($datagridContext); 74 | } 75 | 76 | $datagrid = new Datagrid($datagridContext); 77 | 78 | } catch (Exception $e) { 79 | $datagrid = new EmptyDatagrid(); 80 | } 81 | 82 | return $datagrid; 83 | } 84 | 85 | /** 86 | * @param array $parameters 87 | * @return QueryBuilder 88 | */ 89 | protected function createQueryBuilder(array $parameters) 90 | { 91 | $alias = $this->getAlias(); 92 | 93 | $queryBuilder = $this->queryBuilderFactory->create( 94 | $parameters, 95 | $alias, 96 | $this->dataSchema, 97 | $this->filterStack, 98 | $this->getJoinMap() 99 | ); 100 | 101 | return $queryBuilder; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Builder/Doctrine/Native/DatagridContext.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Builder\Doctrine\Native; 13 | 14 | use Doctrine\DBAL\Query\QueryBuilder; 15 | use Doctrine\ORM\EntityManager; 16 | use Doctrine\ORM\Mapping\ClassMetadata; 17 | use Glavweb\DataSchemaBundle\DataSchema\DataSchema; 18 | use Glavweb\DatagridBundle\Filter\FilterStack; 19 | 20 | /** 21 | * Class DatagridContext 22 | * 23 | * @package Glavweb\DatagridBundle 24 | * @author Andrey Nilov 25 | */ 26 | class DatagridContext 27 | { 28 | /** 29 | * @var string 30 | */ 31 | private $class; 32 | 33 | /** 34 | * @var EntityManager 35 | */ 36 | private $entityManger; 37 | 38 | /** 39 | * @var QueryBuilder 40 | */ 41 | private $queryBuilder; 42 | 43 | /** 44 | * @var FilterStack 45 | */ 46 | private $filterStack; 47 | 48 | /** 49 | * @var DataSchema 50 | */ 51 | private $dataSchema; 52 | 53 | /** 54 | * @var array 55 | */ 56 | private $orderings; 57 | 58 | /** 59 | * @var int 60 | */ 61 | private $firstResult; 62 | 63 | /** 64 | * @var int 65 | */ 66 | private $maxResults; 67 | 68 | /** 69 | * @var string 70 | */ 71 | private $alias; 72 | 73 | /** 74 | * @var array 75 | */ 76 | private $parameters; 77 | 78 | /** 79 | * @var \Doctrine\ORM\Mapping\ClassMetadata 80 | */ 81 | private $classMetadata; 82 | 83 | /** 84 | * DatagridContext constructor. 85 | * 86 | * @param string $class 87 | * @param EntityManager $entityManger 88 | * @param QueryBuilder $queryBuilder 89 | * @param FilterStack $filterStack 90 | * @param DataSchema $dataSchema 91 | * @param array $orderings 92 | * @param int $firstResult 93 | * @param int $maxResults 94 | * @param string $alias 95 | * @param array $parameters 96 | */ 97 | public function __construct( 98 | $class, 99 | EntityManager $entityManger, 100 | QueryBuilder $queryBuilder, 101 | FilterStack $filterStack, 102 | DataSchema $dataSchema, 103 | array $orderings = [], 104 | int $firstResult = 0, 105 | int $maxResults = null, 106 | string $alias = 't', 107 | array $parameters = [] 108 | ) { 109 | $this->class = $class; 110 | $this->entityManger = $entityManger; 111 | $this->queryBuilder = $queryBuilder; 112 | $this->dataSchema = $dataSchema; 113 | $this->filterStack = $filterStack; 114 | $this->orderings = $orderings; 115 | $this->firstResult = $firstResult; 116 | $this->maxResults = $maxResults; 117 | $this->alias = $alias; 118 | $this->parameters = $parameters; 119 | $this->classMetadata = $entityManger->getClassMetadata($class); 120 | } 121 | 122 | /** 123 | * @return string 124 | */ 125 | public function getClass(): string 126 | { 127 | return $this->class; 128 | } 129 | 130 | /** 131 | * @return EntityManager 132 | */ 133 | public function getEntityManger(): EntityManager 134 | { 135 | return $this->entityManger; 136 | } 137 | 138 | /** 139 | * @return QueryBuilder 140 | */ 141 | public function getQueryBuilder(): QueryBuilder 142 | { 143 | return $this->queryBuilder; 144 | } 145 | 146 | /** 147 | * @return FilterStack 148 | */ 149 | public function getFilterStack(): FilterStack 150 | { 151 | return $this->filterStack; 152 | } 153 | 154 | /** 155 | * @return DataSchema 156 | */ 157 | public function getDataSchema(): DataSchema 158 | { 159 | return $this->dataSchema; 160 | } 161 | 162 | /** 163 | * @return array 164 | */ 165 | public function getOrderings(): array 166 | { 167 | return $this->orderings; 168 | } 169 | 170 | /** 171 | * @return int 172 | */ 173 | public function getFirstResult(): int 174 | { 175 | return $this->firstResult; 176 | } 177 | 178 | /** 179 | * @return int|null 180 | */ 181 | public function getMaxResults(): ?int 182 | { 183 | return $this->maxResults; 184 | } 185 | 186 | /** 187 | * @return string 188 | */ 189 | public function getAlias(): string 190 | { 191 | return $this->alias; 192 | } 193 | 194 | /** 195 | * @return array 196 | */ 197 | public function getParameters(): array 198 | { 199 | return $this->parameters; 200 | } 201 | 202 | /** 203 | * @return \Doctrine\ORM\Mapping\ClassMetadata 204 | */ 205 | public function getClassMetadata(): ClassMetadata 206 | { 207 | return $this->classMetadata; 208 | } 209 | } -------------------------------------------------------------------------------- /Builder/Doctrine/ORM/DatagridBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Builder\Doctrine\ORM; 13 | 14 | use Doctrine\ORM\EntityManager; 15 | use Doctrine\ORM\QueryBuilder; 16 | use Glavweb\DatagridBundle\Builder\DatagridBuilderInterface; 17 | use Glavweb\DatagridBundle\Builder\Doctrine\AbstractDatagridBuilder; 18 | use Glavweb\DatagridBundle\Datagrid\Doctrine\ORM\Datagrid; 19 | use Glavweb\DatagridBundle\Datagrid\EmptyDatagrid; 20 | use Glavweb\DatagridBundle\Exception\BuildException; 21 | use Glavweb\DatagridBundle\Exception\Exception; 22 | use Glavweb\DataSchemaBundle\DataSchema\DataSchema; 23 | 24 | /** 25 | * Class Builder 26 | * 27 | * @package Glavweb\DatagridBundle 28 | * @author Andrey Nilov 29 | */ 30 | class DatagridBuilder extends AbstractDatagridBuilder implements DatagridBuilderInterface 31 | { 32 | /** 33 | * @param array $parameters 34 | * @param \Closure $callback 35 | * @return Datagrid 36 | * @throws BuildException 37 | */ 38 | public function build(array $parameters = [], $callback = null) 39 | { 40 | if (!$this->dataSchema instanceof DataSchema) { 41 | throw new BuildException('The Data Schema is not defined.'); 42 | } 43 | 44 | $orderings = $this->getOrderings(); 45 | $firstResult = $this->getFirstResult(); 46 | $maxResults = $this->getMaxResults(); 47 | $alias = $this->getAlias(); 48 | 49 | try { 50 | /** @var EntityManager $em */ 51 | $em = $this->doctrine->getManager(); 52 | $queryBuilder = $this->createQueryBuilder($parameters); 53 | 54 | $datagridContext = new DatagridContext( 55 | $this->getEntityClassName(), 56 | $em, 57 | $queryBuilder, 58 | $this->filterStack, 59 | $this->dataSchema, 60 | (array)$orderings, 61 | (int)$firstResult, 62 | $maxResults, 63 | $alias, 64 | $parameters 65 | ); 66 | 67 | if (is_callable($callback)) { 68 | $callback($datagridContext); 69 | } 70 | 71 | $datagrid = new Datagrid($datagridContext); 72 | 73 | } catch (Exception $e) { 74 | $datagrid = new EmptyDatagrid(); 75 | } 76 | 77 | return $datagrid; 78 | } 79 | 80 | /** 81 | * @param array $parameters 82 | * @return QueryBuilder 83 | */ 84 | protected function createQueryBuilder(array $parameters) 85 | { 86 | $alias = $this->getAlias(); 87 | 88 | $queryBuilder = $this->queryBuilderFactory->create( 89 | $parameters, 90 | $alias, 91 | $this->dataSchema, 92 | $this->filterStack, 93 | $this->getJoinMap() 94 | ); 95 | 96 | return $queryBuilder; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Builder/Doctrine/ORM/DatagridContext.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Builder\Doctrine\ORM; 13 | 14 | use Doctrine\ORM\EntityManager; 15 | use Doctrine\ORM\Query\ResultSetMapping; 16 | use Doctrine\ORM\QueryBuilder; 17 | use Glavweb\DataSchemaBundle\DataSchema\DataSchema; 18 | use Glavweb\DatagridBundle\Filter\FilterStack; 19 | 20 | /** 21 | * Class DatagridContext 22 | * 23 | * @package Glavweb\DatagridBundle 24 | * @author Andrey Nilov 25 | */ 26 | class DatagridContext 27 | { 28 | /** 29 | * @var string 30 | */ 31 | private $class; 32 | 33 | /** 34 | * @var EntityManager 35 | */ 36 | private $entityManger; 37 | 38 | /** 39 | * @var QueryBuilder 40 | */ 41 | private $queryBuilder; 42 | 43 | /** 44 | * @var FilterStack 45 | */ 46 | private $filterStack; 47 | 48 | /** 49 | * @var DataSchema 50 | */ 51 | private $dataSchema; 52 | 53 | /** 54 | * @var array 55 | */ 56 | private $orderings; 57 | 58 | /** 59 | * @var int 60 | */ 61 | private $firstResult; 62 | 63 | /** 64 | * @var int 65 | */ 66 | private $maxResults; 67 | 68 | /** 69 | * @var string 70 | */ 71 | private $alias; 72 | 73 | /** 74 | * @var array 75 | */ 76 | private $resultSetMappingCache = []; 77 | 78 | /** 79 | * @var array 80 | */ 81 | private $parameters; 82 | 83 | /** 84 | * DatagridContext constructor. 85 | * 86 | * @param string $class 87 | * @param EntityManager $entityManger 88 | * @param QueryBuilder $queryBuilder 89 | * @param FilterStack $filterStack 90 | * @param DataSchema $dataSchema 91 | * @param array $orderings 92 | * @param int $firstResult 93 | * @param int $maxResults 94 | * @param string $alias 95 | * @param array $parameters 96 | */ 97 | public function __construct( 98 | $class, 99 | EntityManager $entityManger, 100 | QueryBuilder $queryBuilder, 101 | FilterStack $filterStack, 102 | DataSchema $dataSchema, 103 | array $orderings = null, 104 | int $firstResult = 0, 105 | int $maxResults = null, 106 | string $alias = 't', 107 | array $parameters = [] 108 | ) { 109 | $this->class = $class; 110 | $this->entityManger = $entityManger; 111 | $this->queryBuilder = $queryBuilder; 112 | $this->dataSchema = $dataSchema; 113 | $this->filterStack = $filterStack; 114 | $this->orderings = $orderings; 115 | $this->firstResult = $firstResult; 116 | $this->maxResults = $maxResults; 117 | $this->alias = $alias; 118 | $this->parameters = $parameters; 119 | } 120 | 121 | /** 122 | * @return string 123 | */ 124 | public function getClass(): string 125 | { 126 | return $this->class; 127 | } 128 | 129 | /** 130 | * @return EntityManager 131 | */ 132 | public function getEntityManger(): EntityManager 133 | { 134 | return $this->entityManger; 135 | } 136 | 137 | /** 138 | * @return QueryBuilder 139 | */ 140 | public function getQueryBuilder(): QueryBuilder 141 | { 142 | return $this->queryBuilder; 143 | } 144 | 145 | /** 146 | * @return FilterStack 147 | */ 148 | public function getFilterStack(): FilterStack 149 | { 150 | return $this->filterStack; 151 | } 152 | 153 | /** 154 | * @return DataSchema 155 | */ 156 | public function getDataSchema(): DataSchema 157 | { 158 | return $this->dataSchema; 159 | } 160 | 161 | /** 162 | * @return array 163 | */ 164 | public function getOrderings(): array 165 | { 166 | return $this->orderings; 167 | } 168 | 169 | /** 170 | * @return int 171 | */ 172 | public function getFirstResult(): int 173 | { 174 | return $this->firstResult; 175 | } 176 | 177 | /** 178 | * @return int|null 179 | */ 180 | public function getMaxResults(): ?int 181 | { 182 | return $this->maxResults; 183 | } 184 | 185 | /** 186 | * @return string 187 | */ 188 | public function getAlias(): string 189 | { 190 | return $this->alias; 191 | } 192 | 193 | /** 194 | * @return array 195 | */ 196 | public function getParameters(): array 197 | { 198 | return $this->parameters; 199 | } 200 | 201 | /** 202 | * @return ResultSetMapping 203 | */ 204 | public function getResultSetMapping(): ResultSetMapping 205 | { 206 | $class = $this->class; 207 | $alias = $this->alias; 208 | 209 | $cacheKey = md5($class . '__' .$alias); 210 | if (!isset($this->resultSetMappingCache[$cacheKey])) { 211 | $this->resultSetMappingCache[$cacheKey] = $this->createResultSetMapping($class, $alias); 212 | } 213 | 214 | return $this->resultSetMappingCache[$cacheKey]; 215 | } 216 | 217 | /** 218 | * @return string 219 | */ 220 | public function getSql(): string 221 | { 222 | return $this->buildSql($this->queryBuilder, true); 223 | } 224 | 225 | /** 226 | * @param QueryBuilder $queryBuilder 227 | * @param bool $replaceSelect 228 | * @return string 229 | */ 230 | public function buildSql(QueryBuilder $queryBuilder, $replaceSelect = true): string 231 | { 232 | $sql = $queryBuilder->getQuery()->getSQL(); 233 | 234 | if ($replaceSelect) { 235 | $result = preg_match('/SELECT .*? FROM [\w]* ([^ ]*)/', $sql, $matches); 236 | if (!$result) { 237 | throw new \RuntimeException('Alias not found.'); 238 | } 239 | 240 | $alias = $matches[1]; 241 | $sql = preg_replace('/SELECT .*? FROM/', 'SELECT ' . $alias . '.* FROM', $sql); 242 | } 243 | 244 | return $sql; 245 | } 246 | 247 | /** 248 | * @param string $class 249 | * @param string $alias 250 | * @return ResultSetMapping 251 | */ 252 | protected function createResultSetMapping($class, $alias): ResultSetMapping 253 | { 254 | $em = $this->entityManger; 255 | 256 | $rsm = new ResultSetMapping(); 257 | $rsm->addEntityResult($class, $alias); 258 | 259 | $classMetaData = $em->getClassMetadata($class); 260 | $fieldNames = $classMetaData->getFieldNames(); 261 | foreach ($fieldNames as $fieldName) { 262 | $rsm->addFieldResult($alias, $classMetaData->getColumnName($fieldName), $fieldName); 263 | } 264 | 265 | return $rsm; 266 | } 267 | 268 | /** 269 | * @param array $orderings 270 | * @return array 271 | */ 272 | public function transformOrderingForNativeSql(array $orderings): array 273 | { 274 | $rsm = $this->getResultSetMapping(); 275 | $scalarMappings = $rsm->scalarMappings; 276 | foreach ($orderings as $fieldName => $sort) { 277 | if (($alias = array_search($fieldName, $scalarMappings)) !== false) { 278 | unset($orderings[$fieldName]); 279 | $orderings[$alias] = $sort; 280 | } 281 | } 282 | 283 | return $orderings; 284 | } 285 | } -------------------------------------------------------------------------------- /Builder/Doctrine/ORM/QueryBuilderFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Builder\Doctrine\ORM; 13 | 14 | use Doctrine\Bundle\DoctrineBundle\Registry; 15 | use Doctrine\ORM\EntityRepository; 16 | use Doctrine\ORM\Query\Expr\Join; 17 | use Doctrine\ORM\QueryBuilder; 18 | use Glavweb\DatagridBundle\Builder\Doctrine\AbstractQueryBuilderFactory; 19 | use Glavweb\DatagridBundle\Filter\FilterStack; 20 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinMap; 21 | use Glavweb\DatagridBundle\JoinMap\Doctrine\ORM\JoinBuilder; 22 | use Glavweb\DataSchemaBundle\DataSchema\DataSchema; 23 | use Glavweb\DataSchemaBundle\Service\DataSchemaService; 24 | use Glavweb\DataSchemaBundle\DataSchema\Placeholder; 25 | 26 | /** 27 | * Class QueryBuilderFactory 28 | * 29 | * @package Glavweb\DatagridBundle 30 | * @author Andrey Nilov 31 | */ 32 | class QueryBuilderFactory extends AbstractQueryBuilderFactory 33 | { 34 | /** 35 | * @var JoinBuilder 36 | */ 37 | private $joinBuilder; 38 | 39 | /** 40 | * @var DataSchemaService 41 | */ 42 | private $dataSchemaService; 43 | 44 | /** 45 | * QueryBuilderFactory constructor. 46 | * 47 | * @param Registry $doctrine 48 | * @param Placeholder $placeholder 49 | * @param JoinBuilder $joinBuilder 50 | * @param DataSchemaService $dataSchemaService 51 | */ 52 | public function __construct(Registry $doctrine, Placeholder $placeholder, JoinBuilder $joinBuilder, DataSchemaService $dataSchemaService) 53 | { 54 | parent::__construct($doctrine, $placeholder); 55 | 56 | $this->joinBuilder = $joinBuilder; 57 | $this->dataSchemaService = $dataSchemaService; 58 | } 59 | 60 | /** 61 | * @param array $parameters 62 | * @param string $alias 63 | * @param DataSchema $dataSchema 64 | * @param FilterStack $filterStack 65 | * @param JoinMap|null $joinMap 66 | * @return QueryBuilder 67 | */ 68 | public function create(array $parameters, string $alias, DataSchema $dataSchema, FilterStack $filterStack, JoinMap $joinMap = null) 69 | { 70 | /** @var EntityRepository $repository */ 71 | $dataSchemaConfig = $dataSchema->getConfiguration(); 72 | $repository = $this->doctrine->getRepository($dataSchemaConfig['class']); 73 | 74 | // Create query builder 75 | $queryBuilder = $repository->createQueryBuilder($alias); 76 | 77 | // Apply joins 78 | if (!empty($dataSchemaConfig['conditions'])) { 79 | foreach ($dataSchemaConfig['conditions'] as $conditionConfig) { 80 | if (!$conditionConfig['enabled']) { 81 | continue; 82 | } 83 | 84 | $preparedCondition = $this->placeholder->condition($conditionConfig['condition'], $alias); 85 | if ($preparedCondition) { 86 | $queryBuilder->andWhere($preparedCondition); 87 | } 88 | } 89 | } 90 | 91 | $joinMapFromDataSchema = $this->createJoinMap($dataSchema, $alias); 92 | 93 | if ($joinMapFromDataSchema) { 94 | if ($joinMap) { 95 | $joinMap->merge($joinMapFromDataSchema); 96 | 97 | } else { 98 | $joinMap = $joinMapFromDataSchema; 99 | } 100 | } 101 | 102 | if ($joinMap) { 103 | $this->joinBuilder->apply($queryBuilder, $joinMap); 104 | } 105 | 106 | return $queryBuilder; 107 | } 108 | 109 | /** 110 | * @param DataSchema $dataSchema 111 | * @param string $alias 112 | * @return JoinMap 113 | */ 114 | public function createJoinMap(DataSchema $dataSchema, $alias) 115 | { 116 | $dataSchemaConfig = $dataSchema->getConfiguration(); 117 | $joinMap = new JoinMap($alias, $this->getClassMetadata($dataSchemaConfig['class'])); 118 | 119 | $joins = $this->getJoinsByConfig($dataSchema, $dataSchemaConfig, $alias); 120 | foreach ($joins as $fullPath => $joinData) { 121 | $pathElements = explode('.', $fullPath); 122 | $field = array_pop($pathElements); 123 | $path = implode('.', $pathElements); 124 | 125 | if (($key = array_search($path, $joins)) !== false) { 126 | $path = $key; 127 | } 128 | 129 | $joinFields = $joinData['fields']; 130 | $joinType = $joinData['joinType']; 131 | $conditionType = $joinData['conditionType']; 132 | $condition = $joinData['condition']; 133 | 134 | // If any of these join fields not exist in the class -> join fields is empty 135 | $classMetadata = $this->getClassMetadata($joinData['class']); 136 | $isDifferentFields = (bool)array_diff($joinFields, $classMetadata->getFieldNames()); 137 | if ($isDifferentFields) { 138 | $joinFields = []; 139 | } 140 | 141 | $joinMap->join($path, $field, true, $joinFields, $joinType, $conditionType, $condition); 142 | } 143 | 144 | return $joinMap; 145 | } 146 | 147 | /** 148 | * @param DataSchema $dataSchema 149 | * @param array $config 150 | * @param string $firstAlias 151 | * @param string $alias 152 | * @param array $result 153 | * @return array 154 | */ 155 | private function getJoinsByConfig(DataSchema $dataSchema, array $config, $firstAlias, $alias = null, &$result = []) 156 | { 157 | if (!$alias) { 158 | $alias = $firstAlias; 159 | } 160 | 161 | if (isset($config['properties'])) { 162 | $properties = $config['properties']; 163 | foreach ($properties as $key => $propertyConfig) { 164 | if (isset($propertyConfig['properties']) && !empty($propertyConfig['properties'])) { 165 | $joinType = isset($propertyConfig['join']) && $propertyConfig['join'] !== JoinMap::JOIN_TYPE_NONE ? 166 | $propertyConfig['join'] : false; 167 | 168 | if (!$joinType) { 169 | continue; 170 | } 171 | 172 | $join = $alias . '.' . $key; 173 | $joinAlias = str_replace('.', '_', $join); 174 | 175 | // Join fields 176 | $joinFields = $this->dataSchemaService->getDatabaseFields($propertyConfig); 177 | 178 | $conditionType = $propertyConfig['conditionType'] ?? Join::WITH; 179 | $conditions = $propertyConfig['conditions'] ?? []; 180 | 181 | $preparedConditions = []; 182 | foreach ($conditions as $conditionConfig) { 183 | if (!$conditionConfig['enabled']) { 184 | continue; 185 | } 186 | 187 | $preparedCondition = $dataSchema->conditionPlaceholder($conditionConfig['condition'], $joinAlias); 188 | if ($preparedCondition) { 189 | $preparedConditions[] = '(' . $preparedCondition . ')'; 190 | } 191 | } 192 | $condition = implode('AND', $preparedConditions); 193 | 194 | $result[$join] = [ 195 | 'class' => $propertyConfig['class'], 196 | 'alias' => $joinAlias, 197 | 'fields' => $joinFields, 198 | 'joinType' => $joinType, 199 | 'conditionType' => $conditionType, 200 | 'condition' => $condition, 201 | ]; 202 | 203 | $this->getJoinsByConfig($dataSchema, $propertyConfig, $firstAlias, $joinAlias, $result); 204 | } 205 | } 206 | } 207 | 208 | return $result; 209 | } 210 | } -------------------------------------------------------------------------------- /Datagrid/DatagridInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Datagrid; 13 | 14 | /** 15 | * Interface DatagridInterface 16 | * 17 | * @package Glavweb\DatagridBundle 18 | * @author Andrey Nilov 19 | */ 20 | interface DatagridInterface 21 | { 22 | /** 23 | * @return array 24 | */ 25 | public function getOrderings(); 26 | 27 | /** 28 | * @return int 29 | */ 30 | public function getFirstResult(); 31 | 32 | /** 33 | * @return int 34 | */ 35 | public function getMaxResults(); 36 | 37 | /** 38 | * @return array 39 | */ 40 | public function getItem(); 41 | 42 | /** 43 | * @return array 44 | */ 45 | public function getList(); 46 | 47 | /** 48 | * @return int 49 | */ 50 | public function getTotal(); 51 | } -------------------------------------------------------------------------------- /Datagrid/Doctrine/AbstractDatagrid.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Datagrid\Doctrine; 13 | 14 | use Doctrine\ORM\AbstractQuery; 15 | use Glavweb\DatagridBundle\Datagrid\DatagridInterface; 16 | 17 | /** 18 | * Class AbstractDatagrid 19 | * 20 | * @package Glavweb\DatagridBundle 21 | * @author Andrey Nilov 22 | */ 23 | abstract class AbstractDatagrid implements DatagridInterface 24 | { 25 | /** 26 | * @var array 27 | */ 28 | protected $orderings; 29 | 30 | /** 31 | * @var int 32 | */ 33 | protected $firstResult; 34 | 35 | /** 36 | * @var int 37 | */ 38 | protected $maxResults; 39 | 40 | /** 41 | * @var int|string 42 | */ 43 | protected $hydrationMode = AbstractQuery::HYDRATE_ARRAY; 44 | 45 | /** 46 | * @return array 47 | */ 48 | abstract public function getList(); 49 | 50 | /** 51 | * @return mixed 52 | */ 53 | abstract public function getTotal(); 54 | 55 | /** 56 | * @return array 57 | */ 58 | public function getOrderings() 59 | { 60 | return $this->orderings; 61 | } 62 | 63 | /** 64 | * @return int 65 | */ 66 | public function getFirstResult() 67 | { 68 | return $this->firstResult; 69 | } 70 | 71 | /** 72 | * @return int 73 | */ 74 | public function getMaxResults() 75 | { 76 | return $this->maxResults; 77 | } 78 | 79 | /** 80 | * @return int|string 81 | */ 82 | public function getHydrationMode() 83 | { 84 | return $this->hydrationMode; 85 | } 86 | 87 | /** 88 | * @param int|string $hydrationMode 89 | */ 90 | public function setHydrationMode($hydrationMode) 91 | { 92 | $this->hydrationMode = $hydrationMode; 93 | } 94 | 95 | /** 96 | * @param $parameters 97 | * @return array 98 | */ 99 | protected function clearParameters(array $parameters) 100 | { 101 | $parameters = array_filter($parameters, function ($value) { 102 | if (is_array($value) && empty($value)) { 103 | return false; 104 | } 105 | 106 | return $value !== null; 107 | }); 108 | 109 | return $parameters; 110 | } 111 | } -------------------------------------------------------------------------------- /Datagrid/Doctrine/Native/Datagrid.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Datagrid\Doctrine\Native; 13 | 14 | use Doctrine\DBAL\Query\QueryBuilder; 15 | use Glavweb\DatagridBundle\Builder\Doctrine\Native\DatagridContext; 16 | use Glavweb\DatagridBundle\Datagrid\Doctrine\AbstractDatagrid; 17 | use Glavweb\DatagridBundle\Filter\FilterStack; 18 | use Glavweb\DataSchemaBundle\DataSchema\DataSchema; 19 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinMap; 20 | 21 | /** 22 | * Class Datagrid 23 | * 24 | * @package Glavweb\DatagridBundle 25 | * @author Andrey Nilov 26 | */ 27 | class Datagrid extends AbstractDatagrid 28 | { 29 | /** 30 | * @var QueryBuilder 31 | */ 32 | private $queryBuilder; 33 | 34 | /** 35 | * @var DataSchema 36 | */ 37 | private $dataSchema; 38 | 39 | /** 40 | * @var string 41 | */ 42 | private $alias; 43 | 44 | /** 45 | * @var FilterStack 46 | */ 47 | private $filterStack; 48 | 49 | /** 50 | * @var array 51 | */ 52 | private $parameters; 53 | 54 | /** 55 | * @var \Doctrine\ORM\Mapping\ClassMetadata 56 | */ 57 | private $classMetadata; 58 | 59 | /** 60 | * @param DatagridContext $context 61 | */ 62 | public function __construct(DatagridContext $context) 63 | { 64 | $this->queryBuilder = $context->getQueryBuilder(); 65 | $this->dataSchema = $context->getDataSchema(); 66 | $this->filterStack = $context->getFilterStack(); 67 | $this->orderings = $context->getOrderings(); 68 | $this->firstResult = $context->getFirstResult(); 69 | $this->maxResults = $context->getMaxResults(); 70 | $this->alias = $context->getAlias(); 71 | $this->parameters = $context->getParameters(); 72 | $this->classMetadata = $context->getClassMetadata(); 73 | } 74 | 75 | /** 76 | * @return QueryBuilder 77 | */ 78 | public function getQueryBuilder() 79 | { 80 | return $this->queryBuilder; 81 | } 82 | 83 | /** 84 | * @return string 85 | */ 86 | public function getAlias() 87 | { 88 | return $this->alias; 89 | } 90 | 91 | /** 92 | * @return string 93 | */ 94 | public function getItemAsJson(): string 95 | { 96 | $queryBuilder = clone $this->getQueryBuilder(); 97 | $this->fixQueryBuilder($queryBuilder, 1); 98 | 99 | $queryBuilderWrapper = new QueryBuilder($this->queryBuilder->getConnection()); 100 | $uniqueAlias = uniqid('row_', false); 101 | $queryBuilderWrapper->select(' 102 | ( 103 | SELECT row_to_json(' . $uniqueAlias . ') 104 | FROM (' . $queryBuilder->getSQL() . ') as ' . $uniqueAlias . ' 105 | ) as "data" 106 | '); 107 | 108 | foreach ($queryBuilder->getParameters() as $key => $value) { 109 | $queryBuilderWrapper->setParameter($key, $value); 110 | } 111 | 112 | $result = $queryBuilderWrapper->execute()->fetchAssociative(); 113 | 114 | if (!isset($result['data']) || !$result['data']) { 115 | return '{}'; 116 | } 117 | 118 | return $result['data']; 119 | } 120 | 121 | /** 122 | * @return array 123 | */ 124 | public function getItem(): array 125 | { 126 | $itemData = json_decode($this->getItemAsJson(), true); 127 | 128 | if ($this->dataSchema) { 129 | return $this->dataSchema->getData($itemData); 130 | } 131 | 132 | return $itemData; 133 | } 134 | 135 | /** 136 | * @return string 137 | */ 138 | public function getListAsJson(): string 139 | { 140 | $queryBuilder = clone $this->getQueryBuilder(); 141 | $this->fixQueryBuilder($queryBuilder, $this->getMaxResults()); 142 | 143 | $queryBuilderWrapper = new QueryBuilder($this->queryBuilder->getConnection()); 144 | 145 | $uniqueAlias = uniqid('row_', false); 146 | $queryBuilderWrapper->select(' 147 | ( 148 | SELECT array_to_json(array_agg(row_to_json(' . $uniqueAlias . '))) 149 | FROM (' . $queryBuilder->getSQL() . ') as ' . $uniqueAlias . ' 150 | ) as "data" 151 | '); 152 | 153 | foreach ($queryBuilder->getParameters() as $key => $value) { 154 | $queryBuilderWrapper->setParameter($key, $value); 155 | } 156 | 157 | $result = $queryBuilderWrapper->execute()->fetchAssociative(); 158 | 159 | if (!$result['data']) { 160 | return '[]'; 161 | } 162 | 163 | return $result['data']; 164 | } 165 | 166 | /** 167 | * @return array 168 | */ 169 | public function getList(): array 170 | { 171 | $listData = json_decode($this->getListAsJson(), true); 172 | 173 | if ($this->dataSchema) { 174 | return $this->dataSchema->getList($listData); 175 | } 176 | 177 | return $listData; 178 | } 179 | 180 | /** 181 | * @return int 182 | */ 183 | public function getTotal(): int 184 | { 185 | $queryBuilder = $this->getQueryBuilder(); 186 | $queryBuilder->select('COUNT(*) as count'); 187 | 188 | // Apply filter 189 | $this->applyFilter($queryBuilder); 190 | 191 | $result = $queryBuilder->execute()->fetchAssociative(); 192 | 193 | return (int)$result['count']; 194 | } 195 | 196 | /** 197 | * @param QueryBuilder $queryBuilder 198 | * @param int|null $maxResults 199 | */ 200 | private function fixQueryBuilder(QueryBuilder $queryBuilder, ?int $maxResults) 201 | { 202 | $alias = $this->getAlias(); 203 | $firstResult = $this->getFirstResult(); 204 | 205 | $queryBuilder->setFirstResult($firstResult); 206 | 207 | if ($maxResults) { 208 | $queryBuilder->setMaxResults($maxResults); 209 | } 210 | 211 | $this->applyFilter($queryBuilder); 212 | 213 | 214 | // Apply orderings 215 | $orderings = $this->getOrderings(); 216 | foreach ($orderings as $fieldName => $order) { 217 | if (isset($querySelects[$fieldName]) && $this->dataSchema->hasProperty($fieldName)) { 218 | $queryBuilder->addOrderBy($fieldName, $order); 219 | 220 | continue; 221 | } 222 | 223 | if (!$this->dataSchema->hasPropertyInDb($fieldName)) { 224 | continue; 225 | } 226 | 227 | $sortAlias = $alias; 228 | $propertyConfig = $this->dataSchema->getPropertyConfiguration($fieldName); 229 | $sortColumnName = $propertyConfig['field_db_name']; 230 | 231 | // If the field name have a dot 232 | $fieldNameParts = explode('.', $fieldName); 233 | if (count($fieldNameParts) > 1) { 234 | array_pop($fieldNameParts); 235 | 236 | foreach ($fieldNameParts as $fieldPart) { 237 | $sortAlias = JoinMap::makeAlias($sortAlias, $fieldPart); 238 | } 239 | } 240 | 241 | $queryBuilder->addOrderBy($sortAlias . '.' . $sortColumnName, $order); 242 | } 243 | } 244 | 245 | /** 246 | * @param QueryBuilder $queryBuilder 247 | */ 248 | private function applyFilter(QueryBuilder $queryBuilder): void 249 | { 250 | $alias = $this->getAlias(); 251 | $parameters = $this->clearParameters($this->parameters); 252 | foreach ($parameters as $key => $parameter) { 253 | if (!$parameter || !is_scalar($parameter)) { 254 | continue; 255 | } 256 | 257 | $jsonDecoded = json_decode($parameter); 258 | 259 | if (json_last_error() == JSON_ERROR_NONE) { 260 | $parameters[$key] = $jsonDecoded; 261 | } 262 | } 263 | 264 | foreach ($parameters as $name => $value) { 265 | $filter = $this->filterStack->getByParam($name); 266 | 267 | if (!$filter) { 268 | continue; 269 | } 270 | 271 | $filter->filter($queryBuilder, $alias, $value); 272 | } 273 | } 274 | } -------------------------------------------------------------------------------- /Datagrid/Doctrine/NativeSqlDatagrid.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Datagrid\Doctrine; 13 | 14 | use Doctrine\ORM\NativeQuery; 15 | use Doctrine\ORM\Query; 16 | use Glavweb\DatagridBundle\Builder\Doctrine\ORM\DatagridContext; 17 | use Glavweb\DataSchemaBundle\DataSchema\DataSchema; 18 | 19 | /** 20 | * Class NativeSqlDatagrid 21 | * 22 | * @package Glavweb\DatagridBundle 23 | * @author Andrey Nilov 24 | */ 25 | class NativeSqlDatagrid extends AbstractDatagrid 26 | { 27 | /** 28 | * @var NativeQuery 29 | */ 30 | protected $query; 31 | 32 | /** 33 | * @var DataSchema 34 | */ 35 | private $dataSchema; 36 | 37 | /** 38 | * @var NativeQuery 39 | */ 40 | protected $queryCount; 41 | 42 | /** 43 | * @param NativeQuery $query 44 | * @param DatagridContext $context 45 | */ 46 | public function __construct(NativeQuery $query, DatagridContext $context) 47 | { 48 | $this->query = $query; 49 | $this->queryCount = clone $query; 50 | $this->queryCount->setParameters($query->getParameters()); 51 | 52 | $this->dataSchema = $context->getDataSchema(); 53 | $this->orderings = $context->transformOrderingForNativeSql($context->getOrderings()); 54 | $this->firstResult = $context->getFirstResult(); 55 | $this->maxResults = $context->getMaxResults(); 56 | 57 | if ($this->dataSchema->getHydrationMode() !== null) { 58 | $this->setHydrationMode($this->dataSchema->getHydrationMode()); 59 | } 60 | } 61 | 62 | /** 63 | * @return array 64 | */ 65 | public function getItem() 66 | { 67 | $query = $this->createQuery(); 68 | 69 | $result = $query->getSingleResult($this->getHydrationMode()); 70 | if ($this->dataSchema) { 71 | return $this->dataSchema->getData($result); 72 | } 73 | 74 | return $result; 75 | } 76 | 77 | /** 78 | * @return array 79 | */ 80 | public function getList() 81 | { 82 | $query = $this->createQuery(); 83 | 84 | $result = $query->getResult($this->getHydrationMode()); 85 | if ($this->dataSchema) { 86 | return $this->dataSchema->getList($result); 87 | } 88 | 89 | return $result; 90 | } 91 | 92 | /** 93 | * @return int 94 | */ 95 | public function getTotal() 96 | { 97 | $query = $this->queryCount; 98 | 99 | $sql = $query->getSQL(); 100 | $sql = preg_replace('/SELECT .*? FROM/', 'SELECT COUNT(*) as count FROM', $sql, 1); 101 | $query->setSQL($sql); 102 | 103 | $rsm = new Query\ResultSetMapping(); 104 | $rsm->addScalarResult('count', 'count'); 105 | $query->setResultSetMapping($rsm); 106 | 107 | return (int)$query->getSingleScalarResult(); 108 | } 109 | 110 | /** 111 | * @return NativeQuery 112 | */ 113 | private function createQuery() 114 | { 115 | $query = $this->query; 116 | $sql = $query->getSQL(); 117 | 118 | $orderings = $this->getOrderings(); 119 | if ($orderings) { 120 | $orderParts = []; 121 | foreach ($orderings as $fieldName => $sort) { 122 | $orderParts[] = $fieldName . ' ' . $sort; 123 | } 124 | 125 | $sql .= ' ORDER BY ' . implode(',', $orderParts); 126 | } 127 | 128 | $limit = $this->getMaxResults(); 129 | if ($limit) { 130 | $sql .= ' LIMIT ' . $limit; 131 | } 132 | 133 | $offset = $this->getFirstResult(); 134 | if ($offset) { 135 | $sql .= ' OFFSET ' . $offset; 136 | } 137 | 138 | $query->setSQL($sql); 139 | return $query; 140 | } 141 | } -------------------------------------------------------------------------------- /Datagrid/Doctrine/ORM/Datagrid.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Datagrid\Doctrine\ORM; 13 | 14 | use Doctrine\ORM\Query; 15 | use Doctrine\ORM\QueryBuilder; 16 | use Doctrine\ORM\Tools\Pagination\Paginator; 17 | use Glavweb\DatagridBundle\Builder\Doctrine\ORM\DatagridContext; 18 | use Glavweb\DatagridBundle\Datagrid\Doctrine\AbstractDatagrid; 19 | use Glavweb\DatagridBundle\Filter\FilterStack; 20 | use Glavweb\DataSchemaBundle\DataSchema\DataSchema; 21 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinMap; 22 | 23 | /** 24 | * Class Datagrid 25 | * 26 | * @package Glavweb\DatagridBundle 27 | * @author Andrey Nilov 28 | */ 29 | class Datagrid extends AbstractDatagrid 30 | { 31 | /** 32 | * @var QueryBuilder 33 | */ 34 | private $queryBuilder; 35 | 36 | /** 37 | * @var DataSchema 38 | */ 39 | private $dataSchema; 40 | 41 | /** 42 | * @var string 43 | */ 44 | private $alias; 45 | 46 | /** 47 | * @var array 48 | */ 49 | private $queryHints = []; 50 | 51 | /** 52 | * @var Paginator 53 | */ 54 | private $paginator; 55 | 56 | /** 57 | * @var FilterStack 58 | */ 59 | private $filterStack; 60 | 61 | /** 62 | * @var array 63 | */ 64 | private $parameters; 65 | 66 | /** 67 | * @param DatagridContext $context 68 | */ 69 | public function __construct(DatagridContext $context) 70 | { 71 | $this->queryBuilder = $context->getQueryBuilder(); 72 | $this->dataSchema = $context->getDataSchema(); 73 | $this->filterStack = $context->getFilterStack(); 74 | $this->orderings = $context->getOrderings(); 75 | $this->firstResult = $context->getFirstResult(); 76 | $this->maxResults = $context->getMaxResults(); 77 | $this->alias = $context->getAlias(); 78 | $this->parameters = $context->getParameters(); 79 | 80 | if ($this->dataSchema->getHydrationMode() !== null) { 81 | $this->setHydrationMode($this->dataSchema->getHydrationMode()); 82 | } 83 | } 84 | 85 | /** 86 | * @return QueryBuilder 87 | */ 88 | public function getQueryBuilder() 89 | { 90 | return $this->queryBuilder; 91 | } 92 | 93 | /** 94 | * @return string 95 | */ 96 | public function getAlias() 97 | { 98 | return $this->alias; 99 | } 100 | 101 | /** 102 | * @return array 103 | */ 104 | public function getQueryHints() 105 | { 106 | return $this->queryHints; 107 | } 108 | 109 | /** 110 | * @param array $queryHints 111 | */ 112 | public function setQueryHints($queryHints) 113 | { 114 | $this->queryHints = $queryHints; 115 | } 116 | 117 | /** 118 | * @param string $name 119 | * @param mixed $value 120 | */ 121 | public function setQueryHint($name, $value) 122 | { 123 | $this->queryHints[$name] = $value; 124 | } 125 | 126 | /** 127 | * @return array 128 | */ 129 | public function getItem() 130 | { 131 | $query = $this->createQuery(null); 132 | 133 | $query->setHydrationMode($this->getHydrationMode()); 134 | $this->setHintsToQuery($query); 135 | 136 | $result = $query->getSingleResult(); 137 | if ($this->dataSchema) { 138 | return $this->dataSchema->getData($result); 139 | } 140 | 141 | return $result; 142 | } 143 | 144 | /** 145 | * @return array 146 | */ 147 | public function getList() 148 | { 149 | $paginator = $this->getPaginator(); 150 | 151 | $query = $paginator->getQuery(); 152 | $query->setHydrationMode($this->getHydrationMode()); 153 | $this->setHintsToQuery($query); 154 | 155 | $result = $paginator->getIterator()->getArrayCopy(); 156 | if ($this->dataSchema) { 157 | return $this->dataSchema->getList($result); 158 | } 159 | 160 | return $result; 161 | } 162 | 163 | /** 164 | * @return int 165 | */ 166 | public function getTotal() 167 | { 168 | $paginator = $this->getPaginator(); 169 | 170 | return (int)$paginator->count(); 171 | } 172 | 173 | /** 174 | * @param Query $query 175 | */ 176 | protected function setHintsToQuery(Query $query) 177 | { 178 | $queryHints = $this->getQueryHints(); 179 | foreach ($queryHints as $hintName => $hintValue) { 180 | $query->setHint($hintName, $hintValue); 181 | } 182 | } 183 | 184 | /** 185 | * @return Paginator 186 | */ 187 | protected function getPaginator() 188 | { 189 | if (!$this->paginator) { 190 | $query = $this->createQuery($this->getMaxResults()); 191 | 192 | $this->paginator = new Paginator($query); 193 | } 194 | 195 | return $this->paginator; 196 | } 197 | 198 | /** 199 | * @param int|null $maxResults 200 | * @return Query 201 | */ 202 | private function createQuery(?int $maxResults) 203 | { 204 | $queryBuilder = $this->getQueryBuilder(); 205 | $alias = $this->getAlias(); 206 | $firstResult = $this->getFirstResult(); 207 | 208 | if ($maxResults) { 209 | $queryBuilder->setMaxResults($maxResults); 210 | } 211 | 212 | $queryBuilder->setFirstResult($firstResult); 213 | $queryBuilder->setMaxResults($maxResults); 214 | 215 | // Apply filter 216 | $parameters = $this->clearParameters($this->parameters); 217 | foreach ($parameters as $key => $parameter) { 218 | if (!$parameter || !is_scalar($parameter)) { 219 | continue; 220 | } 221 | 222 | $jsonDecoded = json_decode($parameter); 223 | 224 | if (json_last_error() == JSON_ERROR_NONE) { 225 | $parameters[$key] = $jsonDecoded; 226 | } 227 | } 228 | 229 | foreach ($parameters as $name => $value) { 230 | $filter = $this->filterStack->getByParam($name); 231 | 232 | if (!$filter) { 233 | continue; 234 | } 235 | 236 | $filter->filter($queryBuilder, $alias, $value); 237 | } 238 | 239 | // Apply query selects 240 | $querySelects = $this->dataSchema->getQuerySelects(); 241 | foreach ($querySelects as $propertyName => $querySelect) { 242 | if ($this->dataSchema->hasProperty($propertyName)) { 243 | $queryBuilder->addSelect(sprintf('(%s) as %s', $querySelect, $propertyName)); 244 | } 245 | } 246 | 247 | // Apply orderings 248 | $orderings = $this->getOrderings(); 249 | foreach ($orderings as $fieldName => $order) { 250 | if (isset($querySelects[$fieldName]) && $this->dataSchema->hasProperty($fieldName)) { 251 | $queryBuilder->addOrderBy($fieldName, $order); 252 | 253 | continue; 254 | } 255 | 256 | if (!$this->dataSchema->hasPropertyInDb($fieldName)) { 257 | continue; 258 | } 259 | 260 | $sortAlias = $alias; 261 | $sortFieldName = $fieldName; 262 | 263 | // If the field name have a dot 264 | $fieldNameParts = explode('.', $fieldName); 265 | if (count($fieldNameParts) > 1) { 266 | $sortFieldName = array_pop($fieldNameParts); 267 | 268 | foreach ($fieldNameParts as $fieldPart) { 269 | $sortAlias = JoinMap::makeAlias($sortAlias, $fieldPart); 270 | } 271 | } 272 | 273 | $queryBuilder->addOrderBy($sortAlias . '.' . $sortFieldName, $order); 274 | } 275 | 276 | $query = $queryBuilder->getQuery(); 277 | 278 | return $query; 279 | } 280 | } -------------------------------------------------------------------------------- /Datagrid/EmptyDatagrid.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Datagrid; 13 | 14 | /** 15 | * Class EmptyDatagrid 16 | * 17 | * @package Glavweb\DatagridBundle 18 | * @author Andrey Nilov 19 | */ 20 | class EmptyDatagrid implements DatagridInterface 21 | { 22 | /** 23 | * @return array 24 | */ 25 | public function getOrderings() 26 | { 27 | return []; 28 | } 29 | 30 | /** 31 | * @return int 32 | */ 33 | public function getFirstResult() 34 | { 35 | return 0; 36 | } 37 | 38 | /** 39 | * @return int|null 40 | */ 41 | public function getMaxResults() 42 | { 43 | return null; 44 | } 45 | 46 | /** 47 | * @return array 48 | */ 49 | public function getList() 50 | { 51 | return []; 52 | } 53 | 54 | /** 55 | * @return array 56 | */ 57 | public function getItem() 58 | { 59 | return []; 60 | } 61 | 62 | /** 63 | * @return int 64 | */ 65 | public function getTotal() 66 | { 67 | return 0; 68 | } 69 | } -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\DependencyInjection; 13 | 14 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 15 | use Symfony\Component\Config\Definition\ConfigurationInterface; 16 | 17 | /** 18 | * Class Configuration 19 | * 20 | * This is the class that validates and merges configuration from your app/config files 21 | * 22 | * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class} 23 | * 24 | * @package Glavweb\DatagridBundle 25 | * @author Andrey Nilov 26 | */ 27 | class Configuration implements ConfigurationInterface 28 | { 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function getConfigTreeBuilder() 33 | { 34 | return new TreeBuilder('glavweb_datagrid'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DependencyInjection/GlavwebDatagridExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\DependencyInjection; 13 | 14 | use Symfony\Component\DependencyInjection\ContainerBuilder; 15 | use Symfony\Component\Config\FileLocator; 16 | use Symfony\Component\HttpKernel\DependencyInjection\Extension; 17 | use Symfony\Component\DependencyInjection\Loader; 18 | 19 | /** 20 | * Class GlavwebDatagridExtension 21 | * 22 | * This is the class that loads and manages your bundle configuration 23 | * 24 | * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html} 25 | * 26 | * @package Glavweb\DatagridBundle 27 | * @author Andrey Nilov 28 | */ 29 | class GlavwebDatagridExtension extends Extension 30 | { 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function load(array $configs, ContainerBuilder $container) 35 | { 36 | $configuration = new Configuration(); 37 | $config = $this->processConfiguration($configuration, $configs); 38 | 39 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); 40 | $loader->load('services.yml'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Doctrine/ORM/Functions/Cast.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Doctrine\ORM\Functions; 13 | 14 | use Doctrine\ORM\Query\AST\Functions\FunctionNode; 15 | use Doctrine\ORM\Query\Lexer; 16 | use Doctrine\ORM\Query\Parser; 17 | use Doctrine\ORM\Query\SqlWalker; 18 | 19 | /** 20 | * Class Cast 21 | * 22 | * Created by https://stackoverflow.com/a/46096965/5670350 23 | * 24 | * @package Glavweb\DatagridBundle 25 | */ 26 | class Cast extends FunctionNode 27 | { 28 | /** 29 | * @var \Doctrine\ORM\Query\AST\PathExpression 30 | */ 31 | protected $first; 32 | 33 | /** 34 | * @var string 35 | */ 36 | protected $second; 37 | 38 | /** 39 | * @param SqlWalker $sqlWalker 40 | * 41 | * @return string 42 | */ 43 | public function getSql(SqlWalker $sqlWalker) 44 | { 45 | return sprintf("CAST(%s AS %s)", 46 | $this->first->dispatch($sqlWalker), 47 | $this->second 48 | ); 49 | } 50 | 51 | /** 52 | * @param Parser $parser 53 | * 54 | * @return void 55 | */ 56 | public function parse(Parser $parser) 57 | { 58 | $parser->match(Lexer::T_IDENTIFIER); 59 | $parser->match(Lexer::T_OPEN_PARENTHESIS); 60 | $this->first = $parser->ArithmeticPrimary(); 61 | $parser->match(Lexer::T_AS); 62 | $parser->match(Lexer::T_IDENTIFIER); 63 | $this->second = $parser->getLexer()->token['value']; 64 | $parser->match(Lexer::T_CLOSE_PARENTHESIS); 65 | } 66 | } -------------------------------------------------------------------------------- /Exception/AccessDeniedException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Exception; 13 | 14 | /** 15 | * Class AccessDeniedException 16 | * 17 | * @package Glavweb\DatagridBundle 18 | * @author Andrey Nilov 19 | */ 20 | class AccessDeniedException extends Exception 21 | {} -------------------------------------------------------------------------------- /Exception/BuildException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Exception; 13 | 14 | /** 15 | * Class BuildException 16 | * 17 | * @package Glavweb\DatagridBundle 18 | * @author Andrey Nilov 19 | */ 20 | class BuildException extends Exception 21 | {} -------------------------------------------------------------------------------- /Exception/Exception.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Exception; 13 | 14 | /** 15 | * Class Exception 16 | * 17 | * @package Glavweb\DatagridBundle 18 | * @author Andrey Nilov 19 | */ 20 | class Exception extends \Exception 21 | {} -------------------------------------------------------------------------------- /Factory/DatagridFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Factory; 13 | 14 | use Glavweb\DatagridBundle\Builder\Doctrine\AbstractDatagridBuilder; 15 | 16 | /** 17 | * Class DatagridFactoryInterface 18 | * 19 | * @author Andrey Nilov 20 | */ 21 | interface DatagridFactoryInterface 22 | { 23 | /** 24 | * @param string $dataSchemaFile 25 | * @param string|null $scopeFile 26 | * @return AbstractDatagridBuilder 27 | */ 28 | public function createBuilder(string $dataSchemaFile, string $scopeFile = null): AbstractDatagridBuilder; 29 | } 30 | -------------------------------------------------------------------------------- /Factory/NativeDatagridFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Factory; 13 | 14 | use Doctrine\Bundle\DoctrineBundle\Registry; 15 | use Glavweb\DatagridBundle\Builder\Doctrine\AbstractDatagridBuilder; 16 | use Glavweb\DatagridBundle\Builder\Doctrine\Native\DatagridBuilder; 17 | use Glavweb\DatagridBundle\Builder\Doctrine\Native\QueryBuilderFactory; 18 | use Glavweb\DatagridBundle\Filter\Doctrine\Native\FilterFactory; 19 | use Glavweb\DataSchemaBundle\DataSchema\DataSchemaFactory; 20 | 21 | /** 22 | * Class NativeDatagridFactory 23 | * 24 | * @author Andrey Nilov 25 | */ 26 | class NativeDatagridFactory implements DatagridFactoryInterface 27 | { 28 | /** 29 | * @var Registry 30 | */ 31 | private $doctrine; 32 | 33 | /** 34 | * @var FilterFactory 35 | */ 36 | private $filterFactory; 37 | 38 | /** 39 | * @var DataSchemaFactory 40 | */ 41 | private $dataSchemaFactory; 42 | 43 | /** 44 | * @var QueryBuilderFactory 45 | */ 46 | private $queryBuilderFactory; 47 | 48 | /** 49 | * DatagridFactory constructor. 50 | * 51 | * @param Registry $doctrine 52 | * @param DataSchemaFactory $dataSchemaFactory 53 | * @param FilterFactory $filterFactory 54 | * @param QueryBuilderFactory $queryBuilderFactory 55 | */ 56 | public function __construct(Registry $doctrine, DataSchemaFactory $dataSchemaFactory, FilterFactory $filterFactory, QueryBuilderFactory $queryBuilderFactory) 57 | { 58 | $this->doctrine = $doctrine; 59 | $this->filterFactory = $filterFactory; 60 | $this->dataSchemaFactory = $dataSchemaFactory; 61 | $this->queryBuilderFactory = $queryBuilderFactory; 62 | } 63 | 64 | /** 65 | * @param string $dataSchemaFile 66 | * @param string|null $scopeFile 67 | * @return AbstractDatagridBuilder 68 | */ 69 | public function createBuilder(string $dataSchemaFile, string $scopeFile = null): AbstractDatagridBuilder 70 | { 71 | $builder = new DatagridBuilder( 72 | $this->doctrine, 73 | $this->filterFactory, 74 | $this->dataSchemaFactory, 75 | $this->queryBuilderFactory 76 | ); 77 | 78 | $builder->setDataSchema($dataSchemaFile, $scopeFile); 79 | 80 | return $builder; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Factory/ORMDatagridFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Factory; 13 | 14 | use Doctrine\Bundle\DoctrineBundle\Registry; 15 | use Glavweb\DatagridBundle\Builder\Doctrine\AbstractDatagridBuilder; 16 | use Glavweb\DatagridBundle\Builder\Doctrine\ORM\DatagridBuilder; 17 | use Glavweb\DatagridBundle\Builder\Doctrine\ORM\QueryBuilderFactory; 18 | use Glavweb\DatagridBundle\Filter\Doctrine\ORM\FilterFactory; 19 | use Glavweb\DataSchemaBundle\DataSchema\DataSchemaFactory; 20 | 21 | /** 22 | * Class ORMDatagridFactory 23 | * 24 | * @author Andrey Nilov 25 | */ 26 | class ORMDatagridFactory implements DatagridFactoryInterface 27 | { 28 | /** 29 | * @var Registry 30 | */ 31 | private $doctrine; 32 | 33 | /** 34 | * @var FilterFactory 35 | */ 36 | private $filterFactory; 37 | 38 | /** 39 | * @var DataSchemaFactory 40 | */ 41 | private $dataSchemaFactory; 42 | 43 | /** 44 | * @var QueryBuilderFactory 45 | */ 46 | private $queryBuilderFactory; 47 | 48 | /** 49 | * DatagridFactory constructor. 50 | * 51 | * @param Registry $doctrine 52 | * @param DataSchemaFactory $dataSchemaFactory 53 | * @param FilterFactory $filterFactory 54 | * @param QueryBuilderFactory $queryBuilderFactory 55 | */ 56 | public function __construct(Registry $doctrine, DataSchemaFactory $dataSchemaFactory, FilterFactory $filterFactory, QueryBuilderFactory $queryBuilderFactory) 57 | { 58 | $this->doctrine = $doctrine; 59 | $this->filterFactory = $filterFactory; 60 | $this->dataSchemaFactory = $dataSchemaFactory; 61 | $this->queryBuilderFactory = $queryBuilderFactory; 62 | } 63 | 64 | /** 65 | * @param string $dataSchemaFile 66 | * @param string|null $scopeFile 67 | * @return AbstractDatagridBuilder 68 | */ 69 | public function createBuilder(string $dataSchemaFile, string $scopeFile = null): AbstractDatagridBuilder 70 | { 71 | $builder = new DatagridBuilder( 72 | $this->doctrine, 73 | $this->filterFactory, 74 | $this->dataSchemaFactory, 75 | $this->queryBuilderFactory 76 | ); 77 | 78 | $builder->setDataSchema($dataSchemaFile, $scopeFile); 79 | 80 | return $builder; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Filter/Doctrine/AbstractFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine; 13 | 14 | use Doctrine\ORM\Mapping\ClassMetadata; 15 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinBuilderInterface; 16 | use Glavweb\DatagridBundle\Filter\FilterInterface; 17 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinMap; 18 | 19 | /** 20 | * Class AbstractFilter 21 | * 22 | * @package Glavweb\DatagridBundle 23 | * @author Andrey Nilov 24 | */ 25 | abstract class AbstractFilter implements FilterInterface 26 | { 27 | /** 28 | * Operator types 29 | */ 30 | const EQ = '='; 31 | const NEQ = '<>'; 32 | const LT = '<'; 33 | const LTE = '<='; 34 | const GT = '>'; 35 | const GTE = '>='; 36 | const IN = 'IN'; 37 | const NIN = 'NIN'; 38 | const CONTAINS = 'CONTAINS'; 39 | const NOT_CONTAINS = '!'; 40 | 41 | /** 42 | * @var int 43 | */ 44 | protected static $uniqueParameterId = 0; 45 | 46 | /** 47 | * @var array 48 | */ 49 | protected $options = [ 50 | 'field_type' => null, 51 | 'operator' => null, 52 | 'param_name' => null 53 | ]; 54 | 55 | /** 56 | * @var string 57 | */ 58 | protected $name; 59 | 60 | /** 61 | * @var string 62 | */ 63 | protected $fieldName; 64 | 65 | /** 66 | * @var ClassMetadata 67 | */ 68 | protected $classMetadata; 69 | 70 | /** 71 | * @var JoinBuilderInterface 72 | */ 73 | protected $joinBuilder; 74 | 75 | /** 76 | * @var JoinMap|null 77 | */ 78 | protected $joinMap; 79 | 80 | /** 81 | * @param mixed $queryBuilder 82 | * @param string $alias 83 | * @param string $value 84 | */ 85 | public abstract function filter($queryBuilder, $alias, $value); 86 | 87 | /** 88 | * @return array 89 | */ 90 | protected abstract function getAllowOperators(); 91 | 92 | /** 93 | * Default operator. Use if operator can't defined. 94 | * 95 | * @return string 96 | */ 97 | protected abstract function getDefaultOperator(); 98 | 99 | /** 100 | * @param string $value 101 | * @param array $allowOperators 102 | * @param string $defaultOperator 103 | * @return array 104 | */ 105 | public static function separateOperator($value, array $allowOperators = null, $defaultOperator = self::CONTAINS) 106 | { 107 | $operators = array( 108 | '<>' => self::NEQ, 109 | '<=' => self::LTE, 110 | '>=' => self::GTE, 111 | '<' => self::LT, 112 | '>' => self::GT, 113 | '=' => self::EQ, 114 | '!=' => self::NEQ, 115 | '!' => self::NOT_CONTAINS, 116 | ); 117 | 118 | if ($allowOperators === null) { 119 | $allowOperators = array_keys($operators); 120 | } 121 | 122 | $operator = null; 123 | if (preg_match('/^(?:\s*(<>|<=|>=|<|>|=|!=|!))?(.*)$/', $value, $matches)) { 124 | $operator = isset($operators[$matches[1]]) ? $operators[$matches[1]] : null; 125 | $value = $matches[2]; 126 | } 127 | 128 | if (!$operator || !in_array($operator, $allowOperators)) { 129 | $operator = $defaultOperator; 130 | } 131 | 132 | return array($operator, $value); 133 | } 134 | 135 | /** 136 | * @param string $field 137 | * @return string 138 | */ 139 | public static function makeParamName($field) 140 | { 141 | self::$uniqueParameterId++; 142 | 143 | return str_replace('.', '_', $field) . '_' . self::$uniqueParameterId; 144 | } 145 | 146 | /** 147 | * Filter constructor. 148 | * 149 | * @param string $name 150 | * @param array $options 151 | * @param string $fieldName 152 | * @param ClassMetadata $classMetadata 153 | * @param JoinBuilderInterface $joinBuilder 154 | * @param JoinMap|null $joinMap 155 | */ 156 | public function __construct( 157 | string $name, 158 | array $options, 159 | string $fieldName, 160 | ClassMetadata $classMetadata, 161 | JoinBuilderInterface $joinBuilder, 162 | JoinMap $joinMap = null 163 | ) { 164 | $this->name = $name; 165 | $this->options = array_merge($this->options, $options); 166 | 167 | if (!isset($this->options['param_name'])) { 168 | $this->options['param_name'] = $name; 169 | } 170 | 171 | $this->fieldName = $fieldName; 172 | $this->classMetadata = $classMetadata; 173 | $this->joinBuilder = $joinBuilder; 174 | $this->joinMap = $joinMap; 175 | } 176 | 177 | /** 178 | * @return mixed 179 | */ 180 | public function getName() 181 | { 182 | return $this->name; 183 | } 184 | 185 | /** 186 | * @param mixed $name 187 | */ 188 | public function setName($name) 189 | { 190 | $this->name = $name; 191 | } 192 | 193 | /** 194 | * @return array 195 | */ 196 | public function getOptions() 197 | { 198 | return $this->options; 199 | } 200 | 201 | /** 202 | * @param array $options 203 | */ 204 | public function setOptions($options) 205 | { 206 | $this->options = $options; 207 | } 208 | 209 | /** 210 | * @param string $name 211 | * @return mixed 212 | */ 213 | public function getOption($name) 214 | { 215 | if (!array_key_exists($name, $this->options)) { 216 | throw new \RuntimeException(sprintf('Option "%s" not found.', $name)); 217 | } 218 | 219 | return $this->options[$name]; 220 | } 221 | 222 | /** 223 | * @return JoinMap|null 224 | */ 225 | public function getJoinMap() 226 | { 227 | return $this->joinMap; 228 | } 229 | 230 | /** 231 | * @return bool 232 | */ 233 | public function hasJoinMap() 234 | { 235 | return (bool)$this->getJoinMap(); 236 | } 237 | 238 | /** 239 | * @return mixed 240 | */ 241 | public function getParamName() 242 | { 243 | return $this->getOption('param_name'); 244 | } 245 | 246 | /** 247 | * @param mixed $value 248 | * @param string $currentOperator 249 | * @param array $replaces 250 | * @param array $allowOperators 251 | * @param string $defaultOperator 252 | * @return array 253 | */ 254 | public static function guessOperator($value, $currentOperator = null, array $replaces = [], array $allowOperators = null, $defaultOperator = self::CONTAINS) 255 | { 256 | if (is_array($value)) { 257 | // Validate operator 258 | if ($currentOperator && !in_array($currentOperator, [self::EQ, self::NEQ])) { 259 | throw new \RuntimeException(sprintf('Operator "%s" is not valid.', $currentOperator)); 260 | } 261 | 262 | $operator = $currentOperator == self::NEQ ? self::NIN : self::IN; 263 | 264 | } else { 265 | $value = trim($value); 266 | 267 | $operator = $currentOperator; 268 | if (!$operator) { 269 | list($operator, $value) = self::separateOperator($value, $allowOperators, $defaultOperator); 270 | } 271 | } 272 | 273 | foreach ($replaces as $replaceFrom => $replaceTo) { 274 | if ($operator == $replaceFrom) { 275 | $operator = $replaceTo; 276 | } 277 | } 278 | 279 | return [$operator, $value]; 280 | } 281 | 282 | /** 283 | * @param mixed $value 284 | * @param array $replaces 285 | * @return array 286 | */ 287 | protected function getOperatorAndValue($value, array $replaces = []) 288 | { 289 | $currentOperator = $this->getOption('operator'); 290 | 291 | list($operator, $value) = self::guessOperator( 292 | $value, 293 | $currentOperator, 294 | $replaces, 295 | $this->getAllowOperators(), 296 | $this->getDefaultOperator() 297 | ); 298 | 299 | $this->checkOperator($operator); 300 | 301 | return [$operator, $value]; 302 | } 303 | 304 | /** 305 | * @param string $operator 306 | */ 307 | protected function checkOperator($operator) 308 | { 309 | $allowOperators = $this->getAllowOperators(); 310 | 311 | if (!in_array($operator, $allowOperators)) { 312 | throw new \RuntimeException(sprintf('Operator "%s" not allowed.', $operator)); 313 | } 314 | } 315 | 316 | /** 317 | * @param array $values 318 | * @return bool 319 | */ 320 | protected function existsOperatorsInValues(array $values) 321 | { 322 | foreach ($values as $value) { 323 | list($operator) = self::separateOperator($value, null, null); 324 | 325 | if ($operator) { 326 | return true; 327 | } 328 | } 329 | 330 | return false; 331 | } 332 | 333 | /** 334 | * @param string $fieldName 335 | * @return string 336 | */ 337 | protected function getColumnName(string $fieldName): string 338 | { 339 | return $this->classMetadata->getColumnName($fieldName); 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /Filter/Doctrine/AbstractFilterFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine; 13 | 14 | use Doctrine\Bundle\DoctrineBundle\Registry; 15 | use Doctrine\ORM\EntityManager; 16 | use Doctrine\ORM\Mapping\ClassMetadata; 17 | use Glavweb\DatagridBundle\Filter\FilterInterface; 18 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinBuilderInterface; 19 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinMap; 20 | 21 | /** 22 | * Class AbstractFilterFactory 23 | * 24 | * @package Glavweb\DatagridBundle 25 | * @author Andrey Nilov 26 | */ 27 | abstract class AbstractFilterFactory 28 | { 29 | /** 30 | * @var Registry 31 | */ 32 | protected $doctrine; 33 | 34 | /** 35 | * @var FilterTypeGuesser 36 | */ 37 | protected $filterTypeGuesser; 38 | 39 | /** 40 | * @return array 41 | */ 42 | abstract protected function getTypes(): array; 43 | 44 | /** 45 | * @return JoinBuilderInterface 46 | */ 47 | abstract protected function getJoinBuilder(): JoinBuilderInterface; 48 | 49 | /** 50 | * DoctrineDatagridBuilder constructor. 51 | * 52 | * @param Registry $doctrine 53 | */ 54 | public function __construct(Registry $doctrine) 55 | { 56 | $this->doctrine = $doctrine; 57 | $this->filterTypeGuesser = new FilterTypeGuesser(); 58 | } 59 | 60 | /** 61 | * @param $entityClass 62 | * @param $alias 63 | * @param $name 64 | * @param null $type 65 | * @param array $options 66 | * @return FilterInterface 67 | */ 68 | public function createForEntity($entityClass, $alias, $name, $type = null, $options = []) 69 | { 70 | /** @var EntityManager $em */ 71 | $em = $this->doctrine->getManager(); 72 | $classMetadata = $em->getClassMetadata($entityClass); 73 | 74 | $options = $this->fixOptions($options); 75 | [$fieldName, $classMetadata, $joinMap] = $this->parse($classMetadata, $alias, $name, $options); 76 | 77 | if (!$type) { 78 | $guessType = $this->filterTypeGuesser->guessType($em, $fieldName, $classMetadata, $options); 79 | 80 | $options = array_merge($guessType->getOptions(), $options); 81 | $type = $guessType->getType(); 82 | } 83 | 84 | return $this->createByType($type, $name, $options, $fieldName, $classMetadata, $joinMap); 85 | } 86 | 87 | /** 88 | * @param string $type 89 | * @param string $name 90 | * @param array $options 91 | * @param string $fieldName 92 | * @param ClassMetadata $classMetadata 93 | * @param JoinMap|null $joinMap 94 | * @return FilterInterface 95 | */ 96 | protected function createByType( 97 | string $type, 98 | string $name, 99 | array $options, 100 | string $fieldName, 101 | ClassMetadata $classMetadata, 102 | JoinMap $joinMap = null 103 | ): FilterInterface { 104 | $types = $this->getTypes(); 105 | 106 | if (!isset($types[$type])) { 107 | throw new \RuntimeException(sprintf('Type of filter "%s" is not defined.', $type)); 108 | } 109 | 110 | $class = $types[$type]; 111 | 112 | return new $class($name, $options, $fieldName, $classMetadata, $this->getJoinBuilder(), $joinMap); 113 | } 114 | 115 | /** 116 | * @param ClassMetadata $classMetadata 117 | * @param string $fieldName 118 | * @return mixed 119 | * @throws \Doctrine\ORM\Mapping\MappingException 120 | */ 121 | protected function getAssociationType(ClassMetadata $classMetadata, $fieldName) 122 | { 123 | $type = null; 124 | if ($classMetadata->hasAssociation($fieldName)) { 125 | $associationMapping = $classMetadata->getAssociationMapping($fieldName); 126 | $type = $associationMapping['type']; 127 | } 128 | 129 | return $type; 130 | } 131 | 132 | /** 133 | * @param array $options 134 | * @return array 135 | * @throws \Doctrine\ORM\Mapping\MappingException 136 | */ 137 | private function fixOptions(array $options = []) 138 | { 139 | if (!isset($options['has_select'])) { 140 | $options['has_select'] = true; 141 | } 142 | 143 | return $options; 144 | } 145 | 146 | /** 147 | * @param ClassMetadata $inClassMetadata 148 | * @param string $alias 149 | * @param string $filterName 150 | * @param array $options 151 | * @return array 152 | * @throws \Doctrine\ORM\Mapping\MappingException 153 | */ 154 | private function parse(ClassMetadata $inClassMetadata, $alias, $filterName, array $options = []) 155 | { 156 | $fieldName = $filterName; 157 | $classMetadata = $inClassMetadata; 158 | $joinMap = null; 159 | 160 | /** @var EntityManager $em */ 161 | $em = $this->doctrine->getManager(); 162 | 163 | $joinPath = $alias; 164 | if (strpos($filterName, '.') > 0) { 165 | $filterElements = explode('.', $filterName); 166 | $lastFilterElement = $filterElements[count($filterElements) - 1]; 167 | 168 | $joinMap = new JoinMap($alias, $inClassMetadata); 169 | $joinFieldName = null; 170 | $joinClassMetadata = $inClassMetadata; 171 | foreach ($filterElements as $joinFieldName) { 172 | if ($joinClassMetadata->hasAssociation($joinFieldName)) { 173 | $isLastElement = $joinFieldName == $lastFilterElement; 174 | if (!$isLastElement) { 175 | $joinMap->join($joinPath, $joinFieldName, $options['has_select']); 176 | 177 | $joinAssociationMapping = $joinClassMetadata->getAssociationMapping($joinFieldName); 178 | $joinClassName = $joinAssociationMapping['targetEntity']; 179 | 180 | $joinClassMetadata = $em->getClassMetadata($joinClassName); 181 | $joinPath .= '.' . $joinFieldName; 182 | } 183 | 184 | } else { 185 | break; 186 | } 187 | } 188 | 189 | $classMetadata = $joinClassMetadata; 190 | $fieldName = $joinFieldName; 191 | } 192 | 193 | return [$fieldName, $classMetadata, $joinMap]; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /Filter/Doctrine/FilterTypeGuesser.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine; 13 | 14 | use Doctrine\DBAL\Types\Type; 15 | use Doctrine\ORM\EntityManager; 16 | use Doctrine\ORM\Mapping\ClassMetadata; 17 | use Glavweb\DatagridBundle\Filter\TypeGuess; 18 | 19 | /** 20 | * Class FilterTypeGuesser 21 | * 22 | * @package Glavweb\DatagridBundle 23 | * @author Andrey Nilov 24 | */ 25 | class FilterTypeGuesser 26 | { 27 | /** 28 | * @param EntityManager $entityManager 29 | * @param $propertyName 30 | * @param ClassMetadata $metadata 31 | * @param array $options 32 | * @return TypeGuess 33 | * @throws \Doctrine\DBAL\Exception 34 | */ 35 | public function guessType(EntityManager $entityManager, $propertyName, ClassMetadata $metadata, array $options = []) 36 | { 37 | if (!isset($metadata->fieldMappings[$propertyName]['fieldName'])) { 38 | $found = false; 39 | foreach ($metadata->subClasses as $subClass) { 40 | $subClassMetadata = $entityManager->getClassMetadata($subClass); 41 | 42 | if ($subClassMetadata->hasField($propertyName)) { 43 | $metadata = $subClassMetadata; 44 | $found = true; 45 | break; 46 | } 47 | } 48 | 49 | if (!$found) { 50 | throw new \RuntimeException( 51 | sprintf('Field name "%s" not found in class "%s".', $propertyName, $metadata->getName()) 52 | ); 53 | } 54 | } 55 | 56 | if (isset($metadata->fieldMappings[$propertyName]['id']) && $metadata->fieldMappings[$propertyName]['id']) { 57 | return new TypeGuess('model', $options, TypeGuess::HIGH_CONFIDENCE); 58 | } 59 | 60 | $options['field_name'] = $metadata->fieldMappings[$propertyName]['fieldName']; 61 | $options['field_type'] = $metadata->getTypeOfField($propertyName); 62 | 63 | // Is field type 64 | switch ($options['field_type']) { 65 | case 'boolean': 66 | return new TypeGuess('boolean', $options, TypeGuess::HIGH_CONFIDENCE); 67 | 68 | case 'datetime': 69 | case 'vardatetime': 70 | case 'datetimetz': 71 | case 'time': 72 | case 'date': 73 | return new TypeGuess('datetime', $options, TypeGuess::HIGH_CONFIDENCE); 74 | 75 | case 'decimal': 76 | case 'float': 77 | case 'integer': 78 | case 'bigint': 79 | case 'smallint': 80 | return new TypeGuess('number', $options, TypeGuess::HIGH_CONFIDENCE); 81 | 82 | case 'string': 83 | case 'text': 84 | return new TypeGuess('string', $options, TypeGuess::HIGH_CONFIDENCE); 85 | } 86 | 87 | // If enum type 88 | $reflectionClass = new \ReflectionClass(Type::getType($options['field_type'])); 89 | if ($reflectionClass->isSubclassOf('\Fresh\DoctrineEnumBundle\DBAL\Types\AbstractEnumType')) { 90 | return new TypeGuess('enum', $options, TypeGuess::HIGH_CONFIDENCE); 91 | } 92 | 93 | return new TypeGuess('string', $options, TypeGuess::LOW_CONFIDENCE); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Filter/Doctrine/Native/AbstractFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\Native; 13 | 14 | use Doctrine\DBAL\Query\QueryBuilder; 15 | use Glavweb\DatagridBundle\Filter\FilterInterface; 16 | use Glavweb\DatagridBundle\Filter\Doctrine\AbstractFilter as BaseFilter; 17 | 18 | /** 19 | * Class Filter 20 | * 21 | * @package Glavweb\DatagridBundle 22 | * @author Andrey Nilov 23 | */ 24 | abstract class AbstractFilter extends BaseFilter implements FilterInterface 25 | { 26 | /** 27 | * @param QueryBuilder $queryBuilder 28 | * @param string $alias 29 | * @param string $fieldName 30 | * @param mixed $value 31 | * @return 32 | */ 33 | protected abstract function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value); 34 | 35 | /** 36 | * @param QueryBuilder $queryBuilder 37 | * @param string $alias 38 | * @param mixed $value 39 | */ 40 | public function filter($queryBuilder, $alias, $value) 41 | { 42 | if ($this->joinMap) { 43 | $alias = $this->joinBuilder->apply($queryBuilder, $this->joinMap); 44 | } 45 | 46 | $this->doFilter($queryBuilder, $alias, $this->fieldName, $value); 47 | } 48 | 49 | /** 50 | * @param QueryBuilder $queryBuilder 51 | * @param $operator 52 | * @param $field 53 | * @param $value 54 | */ 55 | protected function executeCondition(QueryBuilder $queryBuilder, $operator, $field, $value) 56 | { 57 | $parameterName = self::makeParamName($field); 58 | $expr = $queryBuilder->expr(); 59 | 60 | if ($operator == self::CONTAINS) { 61 | $value = mb_strtolower($value, 'UTF-8'); 62 | 63 | if ($value === '') { 64 | return; 65 | } 66 | 67 | if (is_numeric($value)) { 68 | $queryBuilder->andWhere($expr->like('CAST(' . $field . ' AS TEXT)', ':' . $parameterName)); 69 | 70 | } else { 71 | $queryBuilder->andWhere($expr->like('LOWER(' . $field . ')', ':' . $parameterName)); 72 | } 73 | 74 | $queryBuilder->setParameter($parameterName, "%$value%"); 75 | 76 | } elseif ($operator == self::NOT_CONTAINS) { 77 | $value = mb_strtolower($value, 'UTF-8'); 78 | 79 | if (is_numeric($value)) { 80 | $queryBuilder->andWhere($expr->notLike('CAST(' . $field . ' AS TEXT)', ':' . $parameterName)); 81 | 82 | } else { 83 | $queryBuilder->andWhere($expr->notLike('LOWER(' . $field . ')', ':' . $parameterName)); 84 | } 85 | 86 | $queryBuilder->setParameter($parameterName, "%$value%"); 87 | 88 | } elseif ($operator == self::IN) { 89 | $queryBuilder->andWhere($expr->in($field, ':' . $parameterName)); 90 | $queryBuilder->setParameter($parameterName, $value); 91 | 92 | } elseif ($operator == self::NIN) { 93 | $queryBuilder->andWhere($expr->notIn($field, ':' . $parameterName)); 94 | $queryBuilder->setParameter($parameterName, $value); 95 | 96 | } else { 97 | $queryBuilder->andWhere($expr->comparison($field, $operator, ':' . $parameterName)); 98 | $queryBuilder->setParameter($parameterName, $value); 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /Filter/Doctrine/Native/BooleanFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\Native; 13 | 14 | use Doctrine\DBAL\Query\QueryBuilder; 15 | 16 | /** 17 | * Class BooleanFilter 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class BooleanFilter extends AbstractFilter 23 | { 24 | /** 25 | * @param QueryBuilder $queryBuilder 26 | * @param string $alias 27 | * @param $fieldName 28 | * @param mixed $value 29 | */ 30 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 31 | { 32 | list($operator, $value) = $this->getOperatorAndValue($value, [ 33 | self::NOT_CONTAINS => self::NEQ, 34 | ]); 35 | 36 | $field = $alias . '.' . $this->getColumnName($fieldName); 37 | $this->executeCondition($queryBuilder, $operator, $field, $value); 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | protected function getAllowOperators() 44 | { 45 | return [ 46 | self::EQ, 47 | self::NEQ, 48 | self::NOT_CONTAINS 49 | ]; 50 | } 51 | 52 | /** 53 | * Default operator. Use if operator can't defined. 54 | * 55 | * @return string 56 | */ 57 | protected function getDefaultOperator() 58 | { 59 | return self::EQ; 60 | } 61 | } -------------------------------------------------------------------------------- /Filter/Doctrine/Native/CallbackFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\Native; 13 | 14 | use Doctrine\DBAL\Query\QueryBuilder; 15 | 16 | /** 17 | * Class CallbackFilter 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class CallbackFilter extends AbstractFilter 23 | { 24 | /** 25 | * @param QueryBuilder $queryBuilder 26 | * @param string $alias 27 | * @param $fieldName 28 | * @param mixed $value 29 | */ 30 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 31 | { 32 | $callback = $this->getOption('callback'); 33 | if (!is_callable($callback)) { 34 | throw new \RuntimeException(sprintf('Please provide a valid callback option "filter" for field "%s"', $this->getName())); 35 | } 36 | 37 | call_user_func($callback, $queryBuilder, $alias, $this->getColumnName($fieldName), $value, $fieldName); 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | protected function getAllowOperators() 44 | { 45 | return []; 46 | } 47 | 48 | /** 49 | * Default operator. Use if operator can't defined. 50 | * 51 | * @return string 52 | */ 53 | protected function getDefaultOperator() 54 | { 55 | return null; 56 | } 57 | } -------------------------------------------------------------------------------- /Filter/Doctrine/Native/DateTimeFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\Native; 13 | 14 | use Doctrine\DBAL\Query\QueryBuilder; 15 | 16 | /** 17 | * Class DateTimeFilter 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class DateTimeFilter extends AbstractFilter 23 | { 24 | /** 25 | * @param QueryBuilder $queryBuilder 26 | * @param string $alias 27 | * @param $fieldName 28 | * @param mixed $value 29 | */ 30 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 31 | { 32 | $executeCondition = function ($field, $inValue) use ($queryBuilder) { 33 | [$operator, $value] = $this->getOperatorAndValue($inValue, $this->replaceOperators()); 34 | 35 | $this->executeCondition($queryBuilder, $operator, $field, $value); 36 | }; 37 | 38 | $field = $alias . '.' . $this->getColumnName($fieldName); 39 | 40 | if (is_array($value) && $this->existsOperatorsInValues($value)) { 41 | foreach ($value as $item) { 42 | $executeCondition($field, $item); 43 | } 44 | 45 | } else { 46 | $executeCondition($field, $value); 47 | } 48 | } 49 | 50 | /** 51 | * @return array 52 | */ 53 | protected function getAllowOperators() 54 | { 55 | return [ 56 | self::EQ, 57 | self::NEQ, 58 | self::LT, 59 | self::LTE, 60 | self::GT, 61 | self::GTE, 62 | self::IN, 63 | self::NIN, 64 | self::CONTAINS, 65 | self::NOT_CONTAINS 66 | ]; 67 | } 68 | 69 | /** 70 | * @return array 71 | */ 72 | protected function replaceOperators(): array 73 | { 74 | return [ 75 | self::CONTAINS => self::EQ, 76 | self::NOT_CONTAINS => self::NEQ 77 | ]; 78 | } 79 | 80 | /** 81 | * Default operator. Use if operator can't defined. 82 | * 83 | * @return string 84 | */ 85 | protected function getDefaultOperator() 86 | { 87 | return self::EQ; 88 | } 89 | } -------------------------------------------------------------------------------- /Filter/Doctrine/Native/EnumFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\Native; 13 | 14 | use Doctrine\DBAL\Query\QueryBuilder; 15 | 16 | /** 17 | * Class EnumFilter 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class EnumFilter extends AbstractFilter 23 | { 24 | /** 25 | * @param QueryBuilder $queryBuilder 26 | * @param string $alias 27 | * @param $fieldName 28 | * @param mixed $value 29 | */ 30 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 31 | { 32 | list($operator, $value) = $this->getOperatorAndValue($value, [ 33 | self::NOT_CONTAINS => self::NEQ, 34 | ]); 35 | 36 | $field = $alias . '.' . $this->getColumnName($fieldName); 37 | $this->executeCondition($queryBuilder, $operator, $field, $value); 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | protected function getAllowOperators() 44 | { 45 | return [ 46 | self::EQ, 47 | self::NEQ, 48 | self::IN, 49 | self::NIN, 50 | self::NOT_CONTAINS 51 | ]; 52 | } 53 | 54 | /** 55 | * Default operator. Use if operator can't defined. 56 | * 57 | * @return string 58 | */ 59 | protected function getDefaultOperator() 60 | { 61 | return self::EQ; 62 | } 63 | } -------------------------------------------------------------------------------- /Filter/Doctrine/Native/FilterFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\Native; 13 | 14 | use Doctrine\Bundle\DoctrineBundle\Registry; 15 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinBuilderInterface; 16 | use Glavweb\DatagridBundle\JoinMap\Doctrine\Native\JoinBuilder; 17 | use Glavweb\DatagridBundle\Filter\Doctrine\AbstractFilterFactory; 18 | 19 | /** 20 | * Class FilterFactory 21 | * @package Glavweb\DatagridBundle 22 | * @author Andrey Nilov 23 | */ 24 | class FilterFactory extends AbstractFilterFactory 25 | { 26 | /** 27 | * @var JoinBuilder 28 | */ 29 | private $joinBuilder; 30 | 31 | /** 32 | * FilterFactory constructor. 33 | * 34 | * @param Registry $doctrine 35 | * @param JoinBuilder $joinBuilder 36 | */ 37 | public function __construct(Registry $doctrine, JoinBuilder $joinBuilder) 38 | { 39 | parent::__construct($doctrine); 40 | 41 | $this->joinBuilder = $joinBuilder; 42 | } 43 | 44 | /** 45 | * @return array 46 | */ 47 | protected function getTypes(): array 48 | { 49 | return [ 50 | 'string' => 'Glavweb\DatagridBundle\Filter\Doctrine\Native\StringFilter', 51 | 'number' => 'Glavweb\DatagridBundle\Filter\Doctrine\Native\NumberFilter', 52 | 'boolean' => 'Glavweb\DatagridBundle\Filter\Doctrine\Native\BooleanFilter', 53 | 'datetime' => 'Glavweb\DatagridBundle\Filter\Doctrine\Native\DateTimeFilter', 54 | 'enum' => 'Glavweb\DatagridBundle\Filter\Doctrine\Native\EnumFilter', 55 | 'model' => 'Glavweb\DatagridBundle\Filter\Doctrine\Native\ModelFilter', 56 | 'callback' => 'Glavweb\DatagridBundle\Filter\Doctrine\Native\CallbackFilter', 57 | ]; 58 | } 59 | 60 | /** 61 | * @return JoinBuilderInterface 62 | */ 63 | protected function getJoinBuilder(): JoinBuilderInterface 64 | { 65 | return $this->joinBuilder; 66 | } 67 | } -------------------------------------------------------------------------------- /Filter/Doctrine/Native/ModelFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\Native; 13 | 14 | use Doctrine\ORM\Mapping\ClassMetadataInfo; 15 | use Doctrine\DBAL\Query\QueryBuilder; 16 | 17 | /** 18 | * Class ModelFilter 19 | * 20 | * @package Glavweb\DatagridBundle 21 | * @author Andrey Nilov 22 | */ 23 | class ModelFilter extends AbstractFilter 24 | { 25 | /** 26 | * @param QueryBuilder $queryBuilder 27 | * @param string $alias 28 | * @param string $fieldName 29 | * @param mixed $value 30 | */ 31 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 32 | { 33 | [$operator, $value] = $this->getOperatorAndValue($value, [ 34 | self::NOT_CONTAINS => self::NEQ, 35 | ]); 36 | 37 | $this->executeCondition($queryBuilder, $operator, $alias . '.' . $fieldName, $value); 38 | } 39 | 40 | /** 41 | * @param QueryBuilder $queryBuilder 42 | * @param string $operator 43 | * @param string $field 44 | * @param mixed $value 45 | */ 46 | protected function executeCondition(QueryBuilder $queryBuilder, $operator, $field, $value) 47 | { 48 | $parameterName = self::makeParamName($field); 49 | $expr = $queryBuilder->expr(); 50 | 51 | if ($operator == self::IN) { 52 | $queryBuilder->andWhere($expr->in($field, ':' . $parameterName)); 53 | 54 | } elseif ($operator == self::NIN) { 55 | $queryBuilder->andWhere($expr->notIn($field, ':' . $parameterName)); 56 | 57 | } elseif ($operator == self::EQ) { 58 | $queryBuilder->andWhere($expr->eq($field, ':' . $parameterName)); 59 | 60 | } elseif ($operator == self::NEQ) { 61 | $queryBuilder->andWhere($expr->neq($field, ':' . $parameterName)); 62 | } 63 | 64 | $queryBuilder->setParameter($parameterName, $value); 65 | } 66 | 67 | /** 68 | * @param string $fieldName 69 | * @return mixed 70 | */ 71 | protected function getAssociationType($fieldName) 72 | { 73 | $associationMapping = $this->classMetadata->getAssociationMapping($fieldName); 74 | $type = $associationMapping['type']; 75 | 76 | return $type; 77 | } 78 | 79 | /** 80 | * @return array 81 | */ 82 | protected function getAllowOperators() 83 | { 84 | return [ 85 | self::EQ, 86 | self::NEQ, 87 | self::IN, 88 | self::NIN, 89 | self::NOT_CONTAINS 90 | ]; 91 | } 92 | 93 | /** 94 | * Default operator. Use if operator can't defined. 95 | * 96 | * @return string 97 | */ 98 | protected function getDefaultOperator() 99 | { 100 | return self::EQ; 101 | } 102 | } -------------------------------------------------------------------------------- /Filter/Doctrine/Native/NumberFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\Native; 13 | 14 | use Doctrine\DBAL\Query\QueryBuilder; 15 | 16 | /** 17 | * Class NumberFilter 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class NumberFilter extends AbstractFilter 23 | { 24 | /** 25 | * @param QueryBuilder $queryBuilder 26 | * @param string $alias 27 | * @param $fieldName 28 | * @param mixed $value 29 | */ 30 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 31 | { 32 | $field = $alias . '.' . $this->getColumnName($fieldName); 33 | 34 | if (is_array($value) && $this->existsOperatorsInValues($value)) { 35 | foreach ($value as $item) { 36 | list($operator, $value) = $this->getOperatorAndValue($item); 37 | 38 | $this->executeCondition($queryBuilder, $operator, $field, $value); 39 | } 40 | 41 | } else { 42 | list($operator, $value) = $this->getOperatorAndValue($value); 43 | 44 | $this->executeCondition($queryBuilder, $operator, $field, $value); 45 | } 46 | } 47 | 48 | /** 49 | * @return array 50 | */ 51 | protected function getAllowOperators() 52 | { 53 | return [ 54 | self::EQ, 55 | self::NEQ, 56 | self::LT, 57 | self::LTE, 58 | self::GT, 59 | self::GTE, 60 | self::IN, 61 | self::NIN, 62 | self::CONTAINS, 63 | self::NOT_CONTAINS 64 | ]; 65 | } 66 | 67 | /** 68 | * Default operator. Use if operator can't defined. 69 | * 70 | * @return string 71 | */ 72 | protected function getDefaultOperator() 73 | { 74 | return self::CONTAINS; 75 | } 76 | } -------------------------------------------------------------------------------- /Filter/Doctrine/Native/StringFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\Native; 13 | 14 | use Doctrine\DBAL\Query\QueryBuilder; 15 | 16 | /** 17 | * Class StringFilter 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class StringFilter extends AbstractFilter 23 | { 24 | /** 25 | * @param QueryBuilder $queryBuilder 26 | * @param string $alias 27 | * @param string $fieldName 28 | * @param mixed $value 29 | */ 30 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 31 | { 32 | $field = $alias . '.' . $this->getColumnName($fieldName); 33 | 34 | if (is_array($value) && $this->existsOperatorsInValues($value)) { 35 | foreach ($value as $item) { 36 | list($operator, $value) = $this->getOperatorAndValue($item); 37 | 38 | $this->executeCondition($queryBuilder, $operator, $field, $value); 39 | } 40 | 41 | } else { 42 | list($operator, $value) = $this->getOperatorAndValue($value); 43 | 44 | $this->executeCondition($queryBuilder, $operator, $field, $value); 45 | } 46 | } 47 | 48 | /** 49 | * @return array 50 | */ 51 | protected function getAllowOperators() 52 | { 53 | return [ 54 | self::EQ, 55 | self::NEQ, 56 | self::IN, 57 | self::NIN, 58 | self::CONTAINS, 59 | self::NOT_CONTAINS 60 | ]; 61 | } 62 | 63 | /** 64 | * Default operator. Use if operator can't defined. 65 | * 66 | * @return string 67 | */ 68 | protected function getDefaultOperator() 69 | { 70 | return self::CONTAINS; 71 | } 72 | } -------------------------------------------------------------------------------- /Filter/Doctrine/ORM/AbstractFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\ORM; 13 | 14 | use Doctrine\ORM\Query\Expr\Comparison; 15 | use Doctrine\ORM\QueryBuilder; 16 | use Glavweb\DatagridBundle\Filter\FilterInterface; 17 | use Glavweb\DatagridBundle\Filter\Doctrine\AbstractFilter as BaseFilter; 18 | 19 | /** 20 | * Class AbstractFilter 21 | * 22 | * @package Glavweb\DatagridBundle 23 | * @author Andrey Nilov 24 | */ 25 | abstract class AbstractFilter extends BaseFilter implements FilterInterface 26 | { 27 | /** 28 | * @param QueryBuilder $queryBuilder 29 | * @param string $alias 30 | * @param string $fieldName 31 | * @param mixed $value 32 | * @return 33 | */ 34 | protected abstract function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value); 35 | 36 | /** 37 | * @param QueryBuilder $queryBuilder 38 | * @param string $alias 39 | * @param mixed $value 40 | */ 41 | public function filter($queryBuilder, $alias, $value) 42 | { 43 | if ($this->joinMap) { 44 | $alias = $this->joinBuilder->apply($queryBuilder, $this->joinMap); 45 | } 46 | 47 | $this->doFilter($queryBuilder, $alias, $this->fieldName, $value); 48 | } 49 | 50 | /** 51 | * @param QueryBuilder $queryBuilder 52 | * @param $operator 53 | * @param $field 54 | * @param $value 55 | */ 56 | protected function executeCondition(QueryBuilder $queryBuilder, $operator, $field, $value) 57 | { 58 | $parameterName = self::makeParamName($field); 59 | $expr = $queryBuilder->expr(); 60 | 61 | if ($operator == self::CONTAINS) { 62 | $value = mb_strtolower($value, 'UTF-8'); 63 | 64 | if ($value === '') { 65 | return; 66 | } 67 | 68 | if (is_numeric($value)) { 69 | $queryBuilder->andWhere($expr->like('CAST(' . $field . ' AS TEXT)', ':' . $parameterName)); 70 | 71 | } else { 72 | $queryBuilder->andWhere($expr->like('LOWER(' . $field . ')', ':' . $parameterName)); 73 | } 74 | 75 | $queryBuilder->setParameter($parameterName, "%$value%"); 76 | 77 | } elseif ($operator == self::NOT_CONTAINS) { 78 | $value = mb_strtolower($value, 'UTF-8'); 79 | 80 | if (is_numeric($value)) { 81 | $queryBuilder->andWhere($expr->notLike('CAST(' . $field . ' AS TEXT)', ':' . $parameterName)); 82 | 83 | } else { 84 | $queryBuilder->andWhere($expr->notLike('LOWER(' . $field . ')', ':' . $parameterName)); 85 | } 86 | 87 | $queryBuilder->setParameter($parameterName, "%$value%"); 88 | 89 | } elseif ($operator == self::IN) { 90 | $queryBuilder->andWhere($expr->in($field, ':' . $parameterName)); 91 | $queryBuilder->setParameter($parameterName, $value); 92 | 93 | } elseif ($operator == self::NIN) { 94 | $queryBuilder->andWhere($expr->notIn($field, ':' . $parameterName)); 95 | $queryBuilder->setParameter($parameterName, $value); 96 | 97 | } else { 98 | $queryBuilder->andWhere(new Comparison($field, $operator, ':' . $parameterName)); 99 | $queryBuilder->setParameter($parameterName, $value); 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /Filter/Doctrine/ORM/BooleanFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\ORM; 13 | 14 | use Doctrine\ORM\QueryBuilder; 15 | 16 | /** 17 | * Class BooleanFilter 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class BooleanFilter extends AbstractFilter 23 | { 24 | /** 25 | * @param QueryBuilder $queryBuilder 26 | * @param string $alias 27 | * @param $fieldName 28 | * @param mixed $value 29 | */ 30 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 31 | { 32 | list($operator, $value) = $this->getOperatorAndValue($value, [ 33 | self::NOT_CONTAINS => self::NEQ, 34 | ]); 35 | 36 | $field = $alias . '.' . $fieldName; 37 | $this->executeCondition($queryBuilder, $operator, $field, $value); 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | protected function getAllowOperators() 44 | { 45 | return [ 46 | self::EQ, 47 | self::NEQ, 48 | self::NOT_CONTAINS 49 | ]; 50 | } 51 | 52 | /** 53 | * Default operator. Use if operator can't defined. 54 | * 55 | * @return string 56 | */ 57 | protected function getDefaultOperator() 58 | { 59 | return self::EQ; 60 | } 61 | } -------------------------------------------------------------------------------- /Filter/Doctrine/ORM/CallbackFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\ORM; 13 | 14 | use Doctrine\ORM\QueryBuilder; 15 | 16 | /** 17 | * Class CallbackFilter 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class CallbackFilter extends AbstractFilter 23 | { 24 | /** 25 | * @param QueryBuilder $queryBuilder 26 | * @param string $alias 27 | * @param $fieldName 28 | * @param mixed $value 29 | */ 30 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 31 | { 32 | $callback = $this->getOption('callback'); 33 | if (!is_callable($callback)) { 34 | throw new \RuntimeException(sprintf('Please provide a valid callback option "filter" for field "%s"', $this->getName())); 35 | } 36 | 37 | call_user_func($callback, $queryBuilder, $alias, $fieldName, $value); 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | protected function getAllowOperators() 44 | { 45 | return []; 46 | } 47 | 48 | /** 49 | * Default operator. Use if operator can't defined. 50 | * 51 | * @return string 52 | */ 53 | protected function getDefaultOperator() 54 | { 55 | return null; 56 | } 57 | } -------------------------------------------------------------------------------- /Filter/Doctrine/ORM/DateTimeFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\ORM; 13 | 14 | use Doctrine\ORM\QueryBuilder; 15 | 16 | /** 17 | * Class DateTimeFilter 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class DateTimeFilter extends AbstractFilter 23 | { 24 | /** 25 | * @param QueryBuilder $queryBuilder 26 | * @param string $alias 27 | * @param $fieldName 28 | * @param mixed $value 29 | */ 30 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 31 | { 32 | $executeCondition = function ($field, $inValue) use ($queryBuilder) { 33 | [$operator, $value] = $this->getOperatorAndValue($inValue, $this->replaceOperators()); 34 | 35 | $this->executeCondition($queryBuilder, $operator, $field, $value); 36 | }; 37 | 38 | $field = $alias . '.' . $fieldName; 39 | 40 | if (is_array($value) && $this->existsOperatorsInValues($value)) { 41 | foreach ($value as $item) { 42 | $executeCondition($field, $item); 43 | } 44 | 45 | } else { 46 | $executeCondition($field, $value); 47 | } 48 | } 49 | 50 | /** 51 | * @return array 52 | */ 53 | protected function getAllowOperators() 54 | { 55 | return [ 56 | self::EQ, 57 | self::NEQ, 58 | self::LT, 59 | self::LTE, 60 | self::GT, 61 | self::GTE, 62 | self::IN, 63 | self::NIN, 64 | self::CONTAINS, 65 | self::NOT_CONTAINS 66 | ]; 67 | } 68 | 69 | /** 70 | * @return array 71 | */ 72 | protected function replaceOperators(): array 73 | { 74 | return [ 75 | self::CONTAINS => self::EQ, 76 | self::NOT_CONTAINS => self::NEQ 77 | ]; 78 | } 79 | 80 | /** 81 | * Default operator. Use if operator can't defined. 82 | * 83 | * @return string 84 | */ 85 | protected function getDefaultOperator() 86 | { 87 | return self::EQ; 88 | } 89 | } -------------------------------------------------------------------------------- /Filter/Doctrine/ORM/EnumFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\ORM; 13 | 14 | use Doctrine\ORM\QueryBuilder; 15 | 16 | /** 17 | * Class EnumFilter 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class EnumFilter extends AbstractFilter 23 | { 24 | /** 25 | * @param QueryBuilder $queryBuilder 26 | * @param string $alias 27 | * @param $fieldName 28 | * @param mixed $value 29 | */ 30 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 31 | { 32 | list($operator, $value) = $this->getOperatorAndValue($value, [ 33 | self::NOT_CONTAINS => self::NEQ, 34 | ]); 35 | 36 | $field = $alias . '.' . $fieldName; 37 | $this->executeCondition($queryBuilder, $operator, $field, $value); 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | protected function getAllowOperators() 44 | { 45 | return [ 46 | self::EQ, 47 | self::NEQ, 48 | self::IN, 49 | self::NIN, 50 | self::NOT_CONTAINS 51 | ]; 52 | } 53 | 54 | /** 55 | * Default operator. Use if operator can't defined. 56 | * 57 | * @return string 58 | */ 59 | protected function getDefaultOperator() 60 | { 61 | return self::EQ; 62 | } 63 | } -------------------------------------------------------------------------------- /Filter/Doctrine/ORM/FilterFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\ORM; 13 | 14 | use Doctrine\Bundle\DoctrineBundle\Registry; 15 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinBuilderInterface; 16 | use Glavweb\DatagridBundle\JoinMap\Doctrine\ORM\JoinBuilder; 17 | use Glavweb\DatagridBundle\Filter\Doctrine\AbstractFilterFactory; 18 | 19 | /** 20 | * Class FilterFactory 21 | * @package Glavweb\DatagridBundle 22 | * @author Andrey Nilov 23 | */ 24 | class FilterFactory extends AbstractFilterFactory 25 | { 26 | /** 27 | * @var JoinBuilder 28 | */ 29 | private $joinBuilder; 30 | 31 | /** 32 | * FilterFactory constructor. 33 | * 34 | * @param Registry $doctrine 35 | * @param JoinBuilder $joinBuilder 36 | */ 37 | public function __construct(Registry $doctrine, JoinBuilder $joinBuilder) 38 | { 39 | parent::__construct($doctrine); 40 | 41 | $this->joinBuilder = $joinBuilder; 42 | } 43 | 44 | /** 45 | * @return array 46 | */ 47 | protected function getTypes(): array 48 | { 49 | return [ 50 | 'string' => 'Glavweb\DatagridBundle\Filter\Doctrine\ORM\StringFilter', 51 | 'number' => 'Glavweb\DatagridBundle\Filter\Doctrine\ORM\NumberFilter', 52 | 'boolean' => 'Glavweb\DatagridBundle\Filter\Doctrine\ORM\BooleanFilter', 53 | 'datetime' => 'Glavweb\DatagridBundle\Filter\Doctrine\ORM\DateTimeFilter', 54 | 'enum' => 'Glavweb\DatagridBundle\Filter\Doctrine\ORM\EnumFilter', 55 | 'model' => 'Glavweb\DatagridBundle\Filter\Doctrine\ORM\ModelFilter', 56 | 'callback' => 'Glavweb\DatagridBundle\Filter\Doctrine\ORM\CallbackFilter', 57 | ]; 58 | } 59 | 60 | /** 61 | * @return JoinBuilderInterface 62 | */ 63 | protected function getJoinBuilder(): JoinBuilderInterface 64 | { 65 | return $this->joinBuilder; 66 | } 67 | } -------------------------------------------------------------------------------- /Filter/Doctrine/ORM/ModelFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\ORM; 13 | 14 | use Doctrine\ORM\Mapping\ClassMetadataInfo; 15 | use Doctrine\ORM\QueryBuilder; 16 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinMap; 17 | 18 | /** 19 | * Class ModelFilter 20 | * 21 | * @package Glavweb\DatagridBundle 22 | * @author Andrey Nilov 23 | */ 24 | class ModelFilter extends AbstractFilter 25 | { 26 | /** 27 | * @param QueryBuilder $queryBuilder 28 | * @param string $alias 29 | * @param string $fieldName 30 | * @param mixed $value 31 | */ 32 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 33 | { 34 | [$operator, $value] = $this->getOperatorAndValue($value, [ 35 | self::NOT_CONTAINS => self::NEQ, 36 | ]); 37 | 38 | $this->executeCondition($queryBuilder, $operator, $alias . '.' . $fieldName, $value); 39 | } 40 | 41 | /** 42 | * @param QueryBuilder $queryBuilder 43 | * @param string $operator 44 | * @param string $field 45 | * @param mixed $value 46 | */ 47 | protected function executeCondition(QueryBuilder $queryBuilder, $operator, $field, $value) 48 | { 49 | $parameterName = self::makeParamName($field); 50 | $expr = $queryBuilder->expr(); 51 | 52 | if ($operator == self::IN) { 53 | $queryBuilder->andWhere($expr->in($field, ':' . $parameterName)); 54 | 55 | } elseif ($operator == self::NIN) { 56 | $queryBuilder->andWhere($expr->notIn($field, ':' . $parameterName)); 57 | 58 | } elseif ($operator == self::EQ) { 59 | $queryBuilder->andWhere($expr->eq($field, ':' . $parameterName)); 60 | 61 | } elseif ($operator == self::NEQ) { 62 | $queryBuilder->andWhere($expr->neq($field, ':' . $parameterName)); 63 | } 64 | 65 | $queryBuilder->setParameter($parameterName, $value); 66 | } 67 | 68 | /** 69 | * @param string $fieldName 70 | * @return mixed 71 | */ 72 | protected function getAssociationType($fieldName) 73 | { 74 | $associationMapping = $this->classMetadata->getAssociationMapping($fieldName); 75 | $type = $associationMapping['type']; 76 | 77 | return $type; 78 | } 79 | 80 | /** 81 | * @return array 82 | */ 83 | protected function getAllowOperators() 84 | { 85 | return [ 86 | self::EQ, 87 | self::NEQ, 88 | self::IN, 89 | self::NIN, 90 | self::NOT_CONTAINS 91 | ]; 92 | } 93 | 94 | /** 95 | * Default operator. Use if operator can't defined. 96 | * 97 | * @return string 98 | */ 99 | protected function getDefaultOperator() 100 | { 101 | return self::EQ; 102 | } 103 | } -------------------------------------------------------------------------------- /Filter/Doctrine/ORM/NumberFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\ORM; 13 | 14 | use Doctrine\ORM\QueryBuilder; 15 | 16 | /** 17 | * Class NumberFilter 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class NumberFilter extends AbstractFilter 23 | { 24 | /** 25 | * @param QueryBuilder $queryBuilder 26 | * @param string $alias 27 | * @param $fieldName 28 | * @param mixed $value 29 | */ 30 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 31 | { 32 | $field = $alias . '.' . $fieldName; 33 | 34 | if (is_array($value) && $this->existsOperatorsInValues($value)) { 35 | foreach ($value as $item) { 36 | list($operator, $value) = $this->getOperatorAndValue($item); 37 | 38 | $this->executeCondition($queryBuilder, $operator, $field, $value); 39 | } 40 | 41 | } else { 42 | list($operator, $value) = $this->getOperatorAndValue($value); 43 | 44 | $this->executeCondition($queryBuilder, $operator, $field, $value); 45 | } 46 | } 47 | 48 | /** 49 | * @return array 50 | */ 51 | protected function getAllowOperators() 52 | { 53 | return [ 54 | self::EQ, 55 | self::NEQ, 56 | self::LT, 57 | self::LTE, 58 | self::GT, 59 | self::GTE, 60 | self::IN, 61 | self::NIN, 62 | self::CONTAINS, 63 | self::NOT_CONTAINS 64 | ]; 65 | } 66 | 67 | /** 68 | * Default operator. Use if operator can't defined. 69 | * 70 | * @return string 71 | */ 72 | protected function getDefaultOperator() 73 | { 74 | return self::CONTAINS; 75 | } 76 | } -------------------------------------------------------------------------------- /Filter/Doctrine/ORM/StringFilter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter\Doctrine\ORM; 13 | 14 | use Doctrine\ORM\QueryBuilder; 15 | 16 | /** 17 | * Class StringFilter 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class StringFilter extends AbstractFilter 23 | { 24 | /** 25 | * @param QueryBuilder $queryBuilder 26 | * @param string $alias 27 | * @param $fieldName 28 | * @param mixed $value 29 | */ 30 | protected function doFilter(QueryBuilder $queryBuilder, $alias, $fieldName, $value) 31 | { 32 | $field = $alias . '.' . $fieldName; 33 | 34 | if (is_array($value) && $this->existsOperatorsInValues($value)) { 35 | foreach ($value as $item) { 36 | list($operator, $value) = $this->getOperatorAndValue($item); 37 | 38 | $this->executeCondition($queryBuilder, $operator, $field, $value); 39 | } 40 | 41 | } else { 42 | list($operator, $value) = $this->getOperatorAndValue($value); 43 | 44 | $this->executeCondition($queryBuilder, $operator, $field, $value); 45 | } 46 | } 47 | 48 | /** 49 | * @return array 50 | */ 51 | protected function getAllowOperators() 52 | { 53 | return [ 54 | self::EQ, 55 | self::NEQ, 56 | self::IN, 57 | self::NIN, 58 | self::CONTAINS, 59 | self::NOT_CONTAINS 60 | ]; 61 | } 62 | 63 | /** 64 | * Default operator. Use if operator can't defined. 65 | * 66 | * @return string 67 | */ 68 | protected function getDefaultOperator() 69 | { 70 | return self::CONTAINS; 71 | } 72 | } -------------------------------------------------------------------------------- /Filter/FilterInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter; 13 | 14 | /** 15 | * Interface FilterInterface 16 | * 17 | * @package Glavweb\DatagridBundle 18 | * @author Andrey Nilov 19 | */ 20 | interface FilterInterface 21 | { 22 | /** 23 | * @return mixed 24 | */ 25 | public function getName(); 26 | 27 | /** 28 | * @return array 29 | */ 30 | public function getOptions(); 31 | 32 | /** 33 | * @param array $options 34 | */ 35 | public function setOptions($options); 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function getParamName(); 41 | 42 | /** 43 | * @param string $name 44 | * @return mixed 45 | */ 46 | public function getOption($name); 47 | 48 | /** 49 | * @param mixed $queryBuilder 50 | * @param string $alias 51 | * @param string $value 52 | */ 53 | public function filter($queryBuilder, $alias, $value); 54 | } -------------------------------------------------------------------------------- /Filter/FilterStack.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\Filter; 13 | 14 | /** 15 | * Class FilterStack 16 | * 17 | * @package Glavweb\DatagridBundle 18 | * @author Andrey Nilov 19 | */ 20 | class FilterStack 21 | { 22 | /** 23 | * @var FilterInterface[] 24 | */ 25 | private $filters = []; 26 | 27 | /** 28 | * @var array 29 | */ 30 | private $filterNamesByParams = []; 31 | 32 | /** 33 | * @param FilterInterface $filter 34 | * @return $this 35 | */ 36 | public function add(FilterInterface $filter) 37 | { 38 | $filterName = $filter->getName(); 39 | $paramName = $filter->getParamName(); 40 | 41 | $this->filters[$filterName] = $filter; 42 | $this->filterNamesByParams[$paramName] = $filterName; 43 | 44 | return $this; 45 | } 46 | 47 | /** 48 | * @param string $filterName 49 | * @return FilterInterface 50 | */ 51 | public function get($filterName) 52 | { 53 | return $this->filters[$filterName]; 54 | } 55 | 56 | /** 57 | * @param string $name 58 | * @return FilterInterface|null 59 | */ 60 | public function getByParam($name) 61 | { 62 | if (!isset($this->filterNamesByParams[$name])) { 63 | return null; 64 | } 65 | 66 | $filterName = $this->filterNamesByParams[$name]; 67 | 68 | return $this->get($filterName); 69 | } 70 | 71 | /** 72 | * @return FilterInterface[] 73 | */ 74 | public function all() 75 | { 76 | return $this->filters; 77 | } 78 | } -------------------------------------------------------------------------------- /Filter/TypeGuess.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class TypeGuess 17 | { 18 | /** 19 | * Marks an instance with a value that is extremely likely to be correct. 20 | * 21 | * @var int 22 | */ 23 | const VERY_HIGH_CONFIDENCE = 3; 24 | 25 | /** 26 | * Marks an instance with a value that is very likely to be correct. 27 | * 28 | * @var int 29 | */ 30 | const HIGH_CONFIDENCE = 2; 31 | 32 | /** 33 | * Marks an instance with a value that is likely to be correct. 34 | * 35 | * @var int 36 | */ 37 | const MEDIUM_CONFIDENCE = 1; 38 | 39 | /** 40 | * Marks an instance with a value that may be correct. 41 | * 42 | * @var int 43 | */ 44 | const LOW_CONFIDENCE = 0; 45 | 46 | /** 47 | * The guessed field type. 48 | * 49 | * @var string 50 | */ 51 | private $type; 52 | 53 | /** 54 | * The guessed options for creating an instance of the guessed class. 55 | * 56 | * @var array 57 | */ 58 | private $options; 59 | 60 | /** 61 | * The confidence about the correctness of the value. 62 | * 63 | * One of VERY_HIGH_CONFIDENCE, HIGH_CONFIDENCE, MEDIUM_CONFIDENCE 64 | * and LOW_CONFIDENCE. 65 | * 66 | * @var int 67 | */ 68 | private $confidence; 69 | 70 | /** 71 | * Constructor. 72 | * 73 | * @param string $type The guessed field type 74 | * @param array $options The options for creating instances of the 75 | * guessed class 76 | * @param int $confidence The confidence that the guessed class name 77 | * is correct 78 | * @throws \InvalidArgumentException if the given value of confidence is unknown 79 | */ 80 | public function __construct($type, array $options, $confidence) 81 | { 82 | if (self::VERY_HIGH_CONFIDENCE !== $confidence && self::HIGH_CONFIDENCE !== $confidence && 83 | self::MEDIUM_CONFIDENCE !== $confidence && self::LOW_CONFIDENCE !== $confidence) { 84 | throw new \InvalidArgumentException('The confidence should be one of the constants defined in Guess.'); 85 | } 86 | 87 | $this->type = $type; 88 | $this->options = $options; 89 | $this->confidence = $confidence; 90 | } 91 | 92 | /** 93 | * Returns the guess most likely to be correct from a list of guesses. 94 | * 95 | * If there are multiple guesses with the same, highest confidence, the 96 | * returned guess is any of them. 97 | * 98 | * @param typeGuess[] $guesses An array of guesses 99 | * 100 | * @return self|null 101 | */ 102 | public static function getBestGuess(array $guesses) 103 | { 104 | $result = null; 105 | $maxConfidence = -1; 106 | 107 | foreach ($guesses as $guess) { 108 | if ($maxConfidence < $confidence = $guess->getConfidence()) { 109 | $maxConfidence = $confidence; 110 | $result = $guess; 111 | } 112 | } 113 | 114 | return $result; 115 | } 116 | 117 | /** 118 | * Returns the confidence that the guessed value is correct. 119 | * 120 | * @return int One of the constants VERY_HIGH_CONFIDENCE, HIGH_CONFIDENCE, 121 | * MEDIUM_CONFIDENCE and LOW_CONFIDENCE 122 | */ 123 | public function getConfidence() 124 | { 125 | return $this->confidence; 126 | } 127 | 128 | /** 129 | * Returns the guessed field type. 130 | * 131 | * @return string 132 | */ 133 | public function getType() 134 | { 135 | return $this->type; 136 | } 137 | 138 | /** 139 | * Returns the guessed options for creating instances of the guessed type. 140 | * 141 | * @return array 142 | */ 143 | public function getOptions() 144 | { 145 | return $this->options; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /GlavwebDatagridBundle.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle; 13 | 14 | use Symfony\Component\HttpKernel\Bundle\Bundle; 15 | 16 | /** 17 | * Class GlavwebDatagridBundle 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class GlavwebDatagridBundle extends Bundle 23 | {} 24 | -------------------------------------------------------------------------------- /JoinMap/Doctrine/JoinBuilderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\JoinMap\Doctrine; 13 | 14 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinMap; 15 | use Doctrine\ORM\QueryBuilder as ORMQueryBuilder; 16 | use Doctrine\DBAL\Query\QueryBuilder as NativeQueryBuilder; 17 | 18 | /** 19 | * Interface JoinBuilderInterface 20 | * 21 | * @package Glavweb\DatagridBundle 22 | * @author Andrey Nilov 23 | */ 24 | interface JoinBuilderInterface 25 | { 26 | /** 27 | * @param NativeQueryBuilder|ORMQueryBuilder $queryBuilder 28 | * @param JoinMap $joinMap 29 | * @return string|null 30 | */ 31 | public function apply($queryBuilder, JoinMap $joinMap): ?string; 32 | } -------------------------------------------------------------------------------- /JoinMap/Doctrine/JoinMap.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\JoinMap\Doctrine; 13 | 14 | use Doctrine\ORM\Mapping\ClassMetadata; 15 | use Doctrine\ORM\Query\Expr\Join; 16 | 17 | /** 18 | * Class JoinMap 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class JoinMap 23 | { 24 | /** 25 | * Constants of join types 26 | */ 27 | const JOIN_TYPE_LEFT = 'left'; 28 | const JOIN_TYPE_INNER = 'inner'; 29 | const JOIN_TYPE_NONE = 'none'; 30 | 31 | /** 32 | * @var string 33 | */ 34 | private $alias; 35 | 36 | /** 37 | * @var ClassMetadata 38 | */ 39 | private $classMetadata; 40 | 41 | /** 42 | * @var array 43 | */ 44 | private $joinMap = []; 45 | 46 | /** 47 | * JoinMap constructor. 48 | * 49 | * @param string $alias 50 | * @param ClassMetadata $classMetadata 51 | */ 52 | public function __construct($alias, ClassMetadata $classMetadata) 53 | { 54 | $this->alias = $alias; 55 | $this->classMetadata = $classMetadata; 56 | 57 | $this->joinMap[$alias] = []; 58 | } 59 | 60 | /** 61 | * @param string $path 62 | * @param string $field 63 | * @return string 64 | */ 65 | public static function makeAlias($path, $field) 66 | { 67 | return str_replace('.', '_', $path) . '_' . $field; 68 | } 69 | 70 | /** 71 | * @param string $path 72 | * @param string $field 73 | * @param bool $hasSelect 74 | * @param array $selectFields 75 | * @param string $joinType 76 | * @param string $conditionType 77 | * @param string $condition 78 | * @return $this 79 | */ 80 | public function join($path, $field, $hasSelect = true, array $selectFields = [], $joinType = 'left', $conditionType = Join::WITH, $condition = null) 81 | { 82 | $this->joinMap[$path][] = [ 83 | 'field' => $field, 84 | 'hasSelect' => $hasSelect, 85 | 'selectFields' => $selectFields, 86 | 'joinType' => $joinType, 87 | 'conditionType' => $conditionType, 88 | 'condition' => $condition 89 | ]; 90 | 91 | return $this; 92 | } 93 | 94 | /** 95 | * @param JoinMap $joinMap 96 | */ 97 | public function merge(JoinMap $joinMap) 98 | { 99 | $this->joinMap = array_merge_recursive($this->joinMap, $joinMap); 100 | } 101 | 102 | /** 103 | * @return array 104 | */ 105 | public function getJoinMap() 106 | { 107 | return $this->joinMap; 108 | } 109 | 110 | /** 111 | * @return string 112 | */ 113 | public function getAlias(): string 114 | { 115 | return $this->alias; 116 | } 117 | 118 | /** 119 | * @return ClassMetadata 120 | */ 121 | public function getClassMetadata(): ClassMetadata 122 | { 123 | return $this->classMetadata; 124 | } 125 | } -------------------------------------------------------------------------------- /JoinMap/Doctrine/ORM/JoinBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Glavweb\DatagridBundle\JoinMap\Doctrine\ORM; 13 | 14 | use Doctrine\ORM\QueryBuilder; 15 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinBuilderInterface; 16 | use Glavweb\DatagridBundle\JoinMap\Doctrine\JoinMap; 17 | 18 | /** 19 | * Class JoinBuilder 20 | * 21 | * @package Glavweb\DatagridBundle 22 | * @author Andrey Nilov 23 | */ 24 | class JoinBuilder implements JoinBuilderInterface 25 | { 26 | /** 27 | * Apply joins and get last alias. 28 | * 29 | * @param QueryBuilder $queryBuilder 30 | * @param JoinMap $joinMap 31 | * @return null|string 32 | */ 33 | public function apply($queryBuilder, JoinMap $joinMap): ?string 34 | { 35 | $alias = null; 36 | 37 | $executedAliases = $queryBuilder->getAllAliases(); 38 | foreach ($joinMap->getJoinMap() as $path => $fields) { 39 | foreach ($fields as $fieldData) { 40 | $field = $fieldData['field']; 41 | $hasSelect = $fieldData['hasSelect']; 42 | $selectFields = $fieldData['selectFields']; 43 | $joinType = $fieldData['joinType']; 44 | $conditionType = $fieldData['conditionType']; 45 | $condition = $fieldData['condition']; 46 | 47 | $pathAlias = str_replace('.', '_', $path); 48 | $alias = $pathAlias . '_' . $field; 49 | $join = $pathAlias . '.' . $field; 50 | 51 | if (in_array($alias, $executedAliases)) { 52 | continue; 53 | } 54 | 55 | if ($hasSelect) { 56 | if (!$selectFields) { 57 | $queryBuilder->addSelect($alias); 58 | 59 | } else { 60 | $queryBuilder->addSelect(sprintf('PARTIAL %s.{%s}', $alias, implode(',', $selectFields))); 61 | } 62 | } 63 | 64 | if ($joinType == JoinMap::JOIN_TYPE_LEFT) { 65 | $queryBuilder->leftJoin($join, $alias, $conditionType, $condition); 66 | 67 | } elseif ($joinType == JoinMap::JOIN_TYPE_INNER) { 68 | $queryBuilder->innerJoin($join, $alias, $conditionType, $condition); 69 | 70 | } else { 71 | throw new \RuntimeException('Join type not defined or has wrong type.'); 72 | } 73 | } 74 | } 75 | 76 | return $alias; 77 | } 78 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 GLAVWEB 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | ### Get the bundle using composer 5 | 6 | Add GlavwebDatagridBundle by running this command from the terminal at the root of 7 | your Symfony project: 8 | 9 | ```bash 10 | php composer.phar require glavweb/datagrid-bundle 11 | ``` 12 | 13 | ### Enable the bundle 14 | 15 | To start using the bundle, register the bundle in your application's kernel class: 16 | 17 | ```php 18 | // app/AppKernel.php 19 | public function registerBundles() 20 | { 21 | $bundles = array( 22 | // ... 23 | new Glavweb\DatagridBundle\GlavwebDatagridBundle(), 24 | // ... 25 | ); 26 | } 27 | ``` 28 | 29 | ### Configure the bundle 30 | 31 | This bundle was designed to just work out of the box. The only thing you have to configure in order to get this bundle up and running is a mapping. 32 | 33 | Basic Usage 34 | =========== 35 | 36 | Define data schema: 37 | 38 | ``` 39 | # app/config/data_schema/article.schema.yml 40 | 41 | schema: 42 | class: AppBundle\Entity\Article 43 | properties: 44 | id: 45 | name: 46 | slug: 47 | body: 48 | ``` 49 | 50 | Define scope: 51 | 52 | ``` 53 | # app/config/scopes/article/short.yml 54 | 55 | scope: 56 | name: 57 | ``` 58 | 59 | Usage in a controller: 60 | 61 | ``` 62 | $datagridBuilder = $this->get('glavweb_datagrid.doctrine_datagrid_builder') 63 | ->setEntityClassName('AppBundle\Entity\Article') 64 | ->setAlias('t') 65 | ->setDataSchema('article.schema.yml', 'article/short.yml') 66 | ->setFirstResult(0) 67 | ->setMaxResults(2) 68 | ->setOrderings(['name' => 'ASC']) 69 | ; 70 | 71 | // Define filters 72 | $datagridBuilder 73 | ->addFilter('name') 74 | ; 75 | 76 | $datagrid = $this->datagridBuilder->build(['name' => 'Article 1']); 77 | $list = $datagrid->getList(); 78 | 79 | ``` 80 | -------------------------------------------------------------------------------- /Resources/config/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | glavweb_datagrid.native_factory: 3 | class: Glavweb\DatagridBundle\Factory\NativeDatagridFactory 4 | public: true 5 | arguments: 6 | - "@doctrine" 7 | - "@glavweb_data_schema.data_schema_factory" 8 | - "@glavweb_datagrid.doctrine_native_filter_factory" 9 | - "@glavweb_datagrid.native_query_builder_factory" 10 | 11 | glavweb_datagrid.orm_factory: 12 | class: Glavweb\DatagridBundle\Factory\ORMDatagridFactory 13 | public: true 14 | arguments: 15 | - "@doctrine" 16 | - "@glavweb_data_schema.data_schema_factory" 17 | - "@glavweb_datagrid.doctrine_orm_filter_factory" 18 | - "@glavweb_datagrid.orm_query_builder_factory" 19 | 20 | glavweb_datagrid.doctrine_native_filter_factory: 21 | class: Glavweb\DatagridBundle\Filter\Doctrine\Native\FilterFactory 22 | arguments: ["@doctrine", "@glavweb_datagrid.native_join_builder"] 23 | 24 | glavweb_datagrid.doctrine_orm_filter_factory: 25 | class: Glavweb\DatagridBundle\Filter\Doctrine\ORM\FilterFactory 26 | arguments: ["@doctrine", "@glavweb_datagrid.orm_join_builder"] 27 | 28 | glavweb_datagrid.native_query_builder_factory: 29 | class: Glavweb\DatagridBundle\Builder\Doctrine\Native\QueryBuilderFactory 30 | arguments: ["@doctrine", "@glavweb_data_schema.placeholder"] 31 | 32 | glavweb_datagrid.orm_query_builder_factory: 33 | class: Glavweb\DatagridBundle\Builder\Doctrine\ORM\QueryBuilderFactory 34 | arguments: ["@doctrine", "@glavweb_data_schema.placeholder", "@glavweb_datagrid.orm_join_builder", "@glavweb_data_schema.service"] 35 | 36 | glavweb_datagrid.native_join_builder: 37 | class: Glavweb\DatagridBundle\JoinMap\Doctrine\Native\JoinBuilder 38 | arguments: ["@doctrine"] 39 | 40 | glavweb_datagrid.orm_join_builder: 41 | class: Glavweb\DatagridBundle\JoinMap\Doctrine\ORM\JoinBuilder 42 | arguments: ["@doctrine"] 43 | 44 | -------------------------------------------------------------------------------- /ci/integration-test/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.2.5-cli 2 | 3 | ARG BUNDLE_VERSION 4 | 5 | RUN apt-get update && \ 6 | apt-get install -y \ 7 | unzip \ 8 | libzip-dev \ 9 | libpq-dev \ 10 | && docker-php-ext-install zip pdo_pgsql 11 | 12 | COPY --from=composer /usr/bin/composer /usr/bin/composer 13 | 14 | WORKDIR /usr/src/ 15 | 16 | RUN composer create-project symfony/skeleton:"^5.3" app 17 | 18 | WORKDIR /usr/src/app 19 | 20 | RUN composer require --dev phpunit/phpunit symfony/test-pack 21 | 22 | COPY environment . 23 | 24 | COPY scripts/ ../scripts/ 25 | RUN chmod +x ../scripts/* 26 | 27 | RUN composer config repositories.glavweb "{\"type\": \"path\", \"url\": \"../bundle\", \"options\": {\"versions\": { \"glavweb/datagrid-bundle\": \"$BUNDLE_VERSION\" }}}" -------------------------------------------------------------------------------- /ci/integration-test/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | db: 5 | image: postgres 6 | environment: 7 | POSTGRES_PASSWORD: password 8 | php: 9 | build: . 10 | depends_on: 11 | - db 12 | volumes: 13 | - ./../..:/usr/src/bundle 14 | - ./../../build/test:/usr/src/build 15 | -------------------------------------------------------------------------------- /ci/integration-test/environment/.env.test: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgresql://postgres:password@db:5432/postgres_test 2 | KERNEL_CLASS='App\Kernel' 3 | SYMFONY_DEPRECATIONS_HELPER=999999 4 | APP_DEBUG=false -------------------------------------------------------------------------------- /ci/integration-test/environment/config/packages/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | dbal: 3 | url: "%env(resolve:DATABASE_URL)%" 4 | charset: UTF8 5 | mapping_types: 6 | enum: string 7 | 8 | orm: 9 | auto_generate_proxy_classes: true 10 | naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware 11 | hydrators: 12 | DatagridHydrator: Glavweb\DataSchemaBundle\Hydrator\Doctrine\DatagridHydrator 13 | 14 | auto_mapping: true 15 | mappings: 16 | App: 17 | is_bundle: false 18 | type: annotation 19 | dir: '%kernel.project_dir%/src/Entity' 20 | prefix: 'App\Entity' 21 | alias: App 22 | 23 | dql: 24 | string_functions: 25 | Cast: Glavweb\DatagridBundle\Doctrine\ORM\Functions\Cast 26 | -------------------------------------------------------------------------------- /ci/integration-test/environment/config/packages/glavweb_data_schema.yaml: -------------------------------------------------------------------------------- 1 | glavweb_data_schema: 2 | default_hydrator_mode: DatagridHydrator 3 | data_schema: 4 | dir: "%kernel.project_dir%/data_schemas" 5 | 6 | scope: 7 | dir: "%kernel.project_dir%/scopes" -------------------------------------------------------------------------------- /ci/integration-test/environment/config/services.yaml: -------------------------------------------------------------------------------- 1 | # This file is the entry point to configure your own services. 2 | # Files in the packages/ subdirectory configure your dependencies. 3 | 4 | # Put parameters here that don't need to change on each machine where the app is deployed 5 | # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration 6 | parameters: 7 | 8 | services: 9 | # default configuration for services in *this* file 10 | _defaults: 11 | autowire: true # Automatically injects dependencies in your services. 12 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. 13 | 14 | # makes classes in src/ available to be used as services 15 | # this creates a service per class whose id is the fully-qualified class name 16 | App\: 17 | resource: '../src/' 18 | exclude: 19 | - '../src/DependencyInjection/' 20 | - '../src/Entity/' 21 | - '../src/Kernel.php' 22 | - '../src/Tests/' 23 | 24 | # add more service definitions when explicit configuration is needed 25 | # please note that last definitions always *replace* previous ones 26 | 27 | app_data_schema_extension: 28 | class: App\DataSchema\AppDataSchemaExtension 29 | tags: 30 | - { name: glavweb_data_schema.extension } -------------------------------------------------------------------------------- /ci/integration-test/environment/data_schemas/article/simple_data.schema.yml: -------------------------------------------------------------------------------- 1 | schema: 2 | class: App\Entity\Article 3 | db_driver: orm 4 | properties: 5 | name: 6 | slug: 7 | body: 8 | -------------------------------------------------------------------------------- /ci/integration-test/environment/data_schemas/event/simple_data.schema.yml: -------------------------------------------------------------------------------- 1 | schema: 2 | class: App\Entity\Event 3 | db_driver: orm 4 | properties: 5 | name: 6 | 7 | articles: 8 | join: none 9 | properties: 10 | name: -------------------------------------------------------------------------------- /ci/integration-test/environment/data_schemas/event_detail/simple_data.schema.yml: -------------------------------------------------------------------------------- 1 | schema: 2 | class: App\Entity\EventDetail 3 | db_driver: orm 4 | properties: 5 | body: 6 | 7 | event: 8 | properties: 9 | name: -------------------------------------------------------------------------------- /ci/integration-test/environment/data_schemas/simple_data.schema.yml: -------------------------------------------------------------------------------- 1 | schema: 2 | class: App\Entity\Article 3 | db_driver: orm 4 | properties: 5 | id: 6 | name: 7 | slug: 8 | body: 9 | -------------------------------------------------------------------------------- /ci/integration-test/environment/data_schemas/test_decode_with_query_selects.schema.yml: -------------------------------------------------------------------------------- 1 | schema: 2 | class: App\Entity\Article 3 | db_driver: orm 4 | query: 5 | selects: 6 | allEvents: 'SELECT COUNT(*) FROM events' 7 | articleEvents: ' 8 | SELECT COUNT(*) 9 | FROM events e 10 | LEFT JOIN article_event AS ae ON ae.event_id = e.id 11 | WHERE ae.article_id = t.id 12 | ' 13 | 14 | properties: 15 | id: { hidden: true } 16 | name: { decode: upper } 17 | slug: { hidden: true } 18 | publishAt: { hidden: true } 19 | allEvents: { source: allEvents } 20 | articleEvents: { source: articleEvents, hidden: true } 21 | slugWithYear: { source: id, decode: concat_slug_with_year } 22 | hasEvents: { source: articleEvents, decode: has_events } 23 | -------------------------------------------------------------------------------- /ci/integration-test/environment/data_schemas/test_decode_with_query_selects_orm.schema.yml: -------------------------------------------------------------------------------- 1 | schema: 2 | class: App\Entity\Article 3 | db_driver: orm 4 | query: 5 | selects: 6 | allEvents: 'SELECT COUNT(e1) FROM App\Entity\Event e1' 7 | articleEvents: ' 8 | SELECT COUNT(e2) 9 | FROM App\Entity\Event e2 10 | LEFT JOIN e2.articles a 11 | WHERE a.id = t.id 12 | ' 13 | 14 | properties: 15 | id: { hidden: true } 16 | name: { decode: upper } 17 | slug: { hidden: true } 18 | publishAt: { hidden: true } 19 | allEvents: { source: allEvents } 20 | articleEvents: { source: articleEvents, hidden: true } 21 | slugWithYear: { source: id, decode: concat_slug_with_year } 22 | hasEvents: { source: articleEvents, decode: has_events } 23 | -------------------------------------------------------------------------------- /ci/integration-test/environment/data_schemas/test_embeds_first_level.schema.yml: -------------------------------------------------------------------------------- 1 | schema: 2 | class: App\Entity\Event 3 | db_driver: orm 4 | properties: 5 | name: 6 | 7 | # Test oneToOne 8 | eventDetail: 9 | join: none 10 | properties: 11 | body: 12 | 13 | # Test manyToOne 14 | eventGroup: 15 | join: none 16 | properties: 17 | name: 18 | 19 | # Test oneToMany 20 | sessions: 21 | join: none 22 | properties: 23 | name: 24 | 25 | # Test manyToMany 26 | tags: 27 | join: none 28 | properties: 29 | name: 30 | -------------------------------------------------------------------------------- /ci/integration-test/environment/data_schemas/test_embeds_second_level.schema.yml: -------------------------------------------------------------------------------- 1 | schema: 2 | class: App\Entity\Article 3 | db_driver: orm 4 | properties: 5 | name: 6 | 7 | events: 8 | join: none 9 | properties: 10 | name: 11 | 12 | # Test oneToOne 13 | eventDetail: 14 | join: none 15 | properties: 16 | body: 17 | 18 | # Test manyToOne 19 | eventGroup: 20 | join: none 21 | properties: 22 | name: 23 | 24 | # Test oneToMany 25 | sessions: 26 | join: none 27 | properties: 28 | name: 29 | 30 | # Test manyToMany 31 | tags: 32 | join: left 33 | properties: 34 | name: 35 | -------------------------------------------------------------------------------- /ci/integration-test/environment/data_schemas/test_joins_first_level.schema.yml: -------------------------------------------------------------------------------- 1 | schema: 2 | class: App\Entity\Event 3 | db_driver: orm 4 | properties: 5 | name: 6 | 7 | # Test oneToOne 8 | eventDetail: 9 | join: left 10 | properties: 11 | body: 12 | 13 | # Test manyToOne 14 | eventGroup: 15 | join: left 16 | properties: 17 | name: 18 | 19 | # Test oneToMany 20 | sessions: 21 | join: left 22 | properties: 23 | name: 24 | 25 | # Test manyToMany 26 | tags: 27 | join: left 28 | properties: 29 | name: 30 | -------------------------------------------------------------------------------- /ci/integration-test/environment/data_schemas/test_joins_second_level.schema.yml: -------------------------------------------------------------------------------- 1 | schema: 2 | class: App\Entity\Article 3 | db_driver: orm 4 | properties: 5 | name: 6 | 7 | events: 8 | join: left 9 | properties: 10 | name: 11 | 12 | # Test oneToOne 13 | eventDetail: 14 | join: left 15 | properties: 16 | body: 17 | 18 | # Test manyToOne 19 | eventGroup: 20 | join: left 21 | properties: 22 | name: 23 | 24 | # Test oneToMany 25 | sessions: 26 | join: left 27 | properties: 28 | name: 29 | 30 | # Test manyToMany 31 | tags: 32 | join: left 33 | properties: 34 | name: 35 | -------------------------------------------------------------------------------- /ci/integration-test/environment/data_schemas/test_load.schema.yml: -------------------------------------------------------------------------------- 1 | schema: 2 | class: App\Entity\Article 3 | db_driver: orm 4 | properties: 5 | id: # integer 6 | name: # string 7 | -------------------------------------------------------------------------------- /ci/integration-test/environment/data_schemas/test_merged.schema.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - {resource: test_load.schema.yml} 3 | 4 | schema: 5 | properties: 6 | slug: # string 7 | body: # string 8 | 9 | -------------------------------------------------------------------------------- /ci/integration-test/environment/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | tests 23 | 24 | 25 | 26 | 27 | 28 | src 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /ci/integration-test/environment/scopes/article/list.yml: -------------------------------------------------------------------------------- 1 | scope: 2 | id: # integer 3 | name: # string 4 | -------------------------------------------------------------------------------- /ci/integration-test/environment/scopes/article/short.yml: -------------------------------------------------------------------------------- 1 | scope: 2 | name: # string 3 | -------------------------------------------------------------------------------- /ci/integration-test/environment/scopes/article/view.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - {resource: list.yml} 3 | 4 | scope: 5 | slug: # string 6 | body: # text 7 | -------------------------------------------------------------------------------- /ci/integration-test/environment/scopes/event/short.yml: -------------------------------------------------------------------------------- 1 | scope: 2 | name: # string 3 | -------------------------------------------------------------------------------- /ci/integration-test/environment/scopes/event_detail/short.yml: -------------------------------------------------------------------------------- 1 | scope: 2 | body: # string 3 | -------------------------------------------------------------------------------- /ci/integration-test/environment/src/DataFixtures/ORM/LoadArticleData.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App\DataFixtures\ORM; 13 | 14 | use App\Entity\Article; 15 | use App\Entity\Event; 16 | use Doctrine\Bundle\FixturesBundle\Fixture; 17 | use Doctrine\Common\DataFixtures\OrderedFixtureInterface; 18 | use Doctrine\Persistence\ObjectManager; 19 | 20 | /** 21 | * Class LoadArticleData 22 | * 23 | * @author Andrey Nilov 24 | * @package Glavweb\DatagridBundle 25 | */ 26 | class LoadArticleData extends Fixture implements OrderedFixtureInterface 27 | { 28 | /** 29 | * @param ObjectManager $manager 30 | */ 31 | public function load(ObjectManager $manager) 32 | { 33 | $data = [ 34 | [ 35 | 'name' => 'Article 1', 36 | 'slug' => 'article-1', 37 | 'body' => 'Article about Katy', 38 | 'countEvents' => 2, 39 | 'publish' => true, 40 | 'publishAt' => '2016-05-09 10:00', 41 | 'events' => ['event-1', 'event-2'], 42 | 'reference' => 'article-1' 43 | ], 44 | [ 45 | 'name' => 'Article 2', 46 | 'slug' => 'article-2', 47 | 'body' => 'Article about Mary', 48 | 'countEvents' => 1, 49 | 'publish' => false, 50 | 'publishAt' => '2016-05-10 11:00', 51 | 'events' => ['event-3'], 52 | 'reference' => 'article-2' 53 | ], 54 | [ 55 | 'name' => 'Article 3', 56 | 'slug' => 'article-3', 57 | 'body' => 'Article about Niki', 58 | 'countEvents' => 0, 59 | 'publish' => false, 60 | 'publishAt' => '2016-05-11 12:00', 61 | 'events' => [], 62 | 'reference' => 'article-3' 63 | ], 64 | ]; 65 | 66 | foreach ($data as $item) { 67 | $article = new Article(); 68 | $article->setName($item['name']); 69 | $article->setSlug($item['slug']); 70 | $article->setBody($item['body']); 71 | $article->setCountEvents($item['countEvents']); 72 | $article->setPublish($item['publish']); 73 | $article->setPublishAt(new \DateTime($item['publishAt'])); 74 | 75 | foreach ($item['events'] as $event) { 76 | /** @var Event $event */ 77 | $event = $this->getReference($event); 78 | $article->addEvent($event); 79 | } 80 | 81 | $manager->persist($article); 82 | 83 | $this->addReference($item['reference'], $article); 84 | } 85 | 86 | $manager->flush(); 87 | } 88 | 89 | public function getOrder() 90 | { 91 | return 3; 92 | } 93 | } -------------------------------------------------------------------------------- /ci/integration-test/environment/src/DataFixtures/ORM/LoadEventData.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App\DataFixtures\ORM; 13 | 14 | use Doctrine\Bundle\FixturesBundle\Fixture; 15 | use Doctrine\Common\DataFixtures\OrderedFixtureInterface; 16 | use Doctrine\Persistence\ObjectManager; 17 | use App\Entity\Event; 18 | use App\Entity\EventDetail; 19 | use App\Entity\EventGroup; 20 | use App\Entity\Tag; 21 | 22 | /** 23 | * Class LoadEventData 24 | * 25 | * @author Andrey Nilov 26 | * @package Glavweb\DatagridBundle 27 | */ 28 | class LoadEventData extends Fixture implements OrderedFixtureInterface 29 | { 30 | /** 31 | * @param ObjectManager $manager 32 | */ 33 | public function load(ObjectManager $manager) 34 | { 35 | 36 | $data = [ 37 | [ 38 | 'name' => 'Event 1', 39 | 'eventDetail' => 'event-detail-1', 40 | 'eventGroup' => 'event-group-1', 41 | 'tags' => ['tag-1', 'tag-2'], 42 | 'reference' => 'event-1' 43 | ], 44 | [ 45 | 'name' => 'Event 2', 46 | 'eventDetail' => 'event-detail-2', 47 | 'eventGroup' => 'event-group-1', 48 | 'tags' => ['tag-3'], 49 | 'reference' => 'event-2' 50 | ], 51 | [ 52 | 'name' => 'Event 3', 53 | 'eventDetail' => 'event-detail-3', 54 | 'eventGroup' => 'event-group-2', 55 | 'tags' => ['tag-4', 'tag-5'], 56 | 'reference' => 'event-3' 57 | ], 58 | ]; 59 | 60 | foreach ($data as $item) { 61 | /** @var EventDetail $eventDetail */ 62 | /** @var EventGroup $eventGroup */ 63 | $eventDetail = $this->getReference($item['eventDetail']); 64 | $eventGroup = $this->getReference($item['eventGroup']); 65 | 66 | $event = new Event(); 67 | $event->setName($item['name']); 68 | $event->setEventDetail($eventDetail); 69 | $event->setEventGroup($eventGroup); 70 | 71 | foreach ($item['tags'] as $tag) { 72 | /** @var Tag $tag */ 73 | $tag = $this->getReference($tag); 74 | $event->addTag($tag); 75 | } 76 | 77 | $manager->persist($event); 78 | 79 | $this->addReference($item['reference'], $event); 80 | } 81 | 82 | $manager->flush(); 83 | } 84 | 85 | /** 86 | * Set loading order. 87 | * 88 | * @return int 89 | */ 90 | public function getOrder() 91 | { 92 | return 2; 93 | } 94 | } -------------------------------------------------------------------------------- /ci/integration-test/environment/src/DataFixtures/ORM/LoadEventDetailData.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App\DataFixtures\ORM; 13 | 14 | use Doctrine\Bundle\FixturesBundle\Fixture; 15 | use Doctrine\Common\DataFixtures\OrderedFixtureInterface; 16 | use Doctrine\Persistence\ObjectManager; 17 | use App\Entity\EventDetail; 18 | 19 | /** 20 | * Class LoadEventDetailData 21 | * 22 | * @author Andrey Nilov 23 | * @package Glavweb\DatagridBundle 24 | */ 25 | class LoadEventDetailData extends Fixture implements OrderedFixtureInterface 26 | { 27 | /** 28 | * @param ObjectManager $manager 29 | */ 30 | public function load(ObjectManager $manager) 31 | { 32 | $data = [ 33 | ['body' => 'Body for event detail 1', 'reference' => 'event-detail-1'], 34 | ['body' => 'Body for event detail 2', 'reference' => 'event-detail-2'], 35 | ['body' => 'Body for event detail 3', 'reference' => 'event-detail-3'], 36 | ]; 37 | 38 | foreach ($data as $item) { 39 | $eventDetail = new EventDetail(); 40 | $eventDetail->setBody($item['body']); 41 | $manager->persist($eventDetail); 42 | 43 | $this->addReference($item['reference'], $eventDetail); 44 | } 45 | 46 | $manager->flush(); 47 | } 48 | 49 | /** 50 | * Set loading order. 51 | * 52 | * @return int 53 | */ 54 | public function getOrder() 55 | { 56 | return 1; 57 | } 58 | } -------------------------------------------------------------------------------- /ci/integration-test/environment/src/DataFixtures/ORM/LoadEventGroupData.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App\DataFixtures\ORM; 13 | 14 | use Doctrine\Bundle\FixturesBundle\Fixture; 15 | use Doctrine\Common\DataFixtures\OrderedFixtureInterface; 16 | use Doctrine\Persistence\ObjectManager; 17 | use App\Entity\EventGroup; 18 | 19 | /** 20 | * Class LoadEventGroupData 21 | * 22 | * @author Andrey Nilov 23 | * @package Glavweb\DatagridBundle 24 | */ 25 | class LoadEventGroupData extends Fixture implements OrderedFixtureInterface 26 | { 27 | /** 28 | * @param ObjectManager $manager 29 | */ 30 | public function load(ObjectManager $manager) 31 | { 32 | $data = [ 33 | ['name' => 'Event group 1', 'reference' => 'event-group-1'], 34 | ['name' => 'Event group 2', 'reference' => 'event-group-2'], 35 | ]; 36 | 37 | foreach ($data as $item) { 38 | $eventGroup = new EventGroup(); 39 | $eventGroup->setName($item['name']); 40 | $manager->persist($eventGroup); 41 | 42 | $this->addReference($item['reference'], $eventGroup); 43 | } 44 | 45 | $manager->flush(); 46 | } 47 | 48 | /** 49 | * Set loading order. 50 | * 51 | * @return int 52 | */ 53 | public function getOrder() 54 | { 55 | return 1; 56 | } 57 | } -------------------------------------------------------------------------------- /ci/integration-test/environment/src/DataFixtures/ORM/LoadSessionData.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App\DataFixtures\ORM; 13 | 14 | use Doctrine\Bundle\FixturesBundle\Fixture; 15 | use Doctrine\Common\DataFixtures\OrderedFixtureInterface; 16 | use Doctrine\Persistence\ObjectManager; 17 | use App\Entity\Event; 18 | use App\Entity\Session; 19 | 20 | /** 21 | * Class LoadSessionData 22 | * 23 | * @author Andrey Nilov 24 | * @package Glavweb\DatagridBundle 25 | */ 26 | class LoadSessionData extends Fixture implements OrderedFixtureInterface 27 | { 28 | /** 29 | * @param ObjectManager $manager 30 | */ 31 | public function load(ObjectManager $manager) 32 | { 33 | $data = [ 34 | ['name' => 'Session 1', 'event' => 'event-1', 'reference' => 'session-1'], 35 | ['name' => 'Session 2', 'event' => 'event-1', 'reference' => 'session-2'], 36 | ['name' => 'Session 3', 'event' => 'event-2', 'reference' => 'session-3'], 37 | ['name' => 'Session 4', 'event' => 'event-2', 'reference' => 'session-4'], 38 | ['name' => 'Session 5', 'event' => 'event-2', 'reference' => 'session-5'], 39 | ['name' => 'Session 6', 'event' => 'event-2', 'reference' => 'session-6'], 40 | ['name' => 'Session 7', 'event' => 'event-2', 'reference' => 'session-7'], 41 | ['name' => 'Session 8', 'event' => 'event-3', 'reference' => 'session-8'], 42 | ['name' => 'Session 9', 'event' => 'event-3', 'reference' => 'session-9'], 43 | ['name' => 'Session 10', 'event' => 'event-3', 'reference' => 'session-10'], 44 | ['name' => 'Session 11', 'event' => 'event-3', 'reference' => 'session-11'], 45 | ['name' => 'Session 12', 'event' => 'event-3', 'reference' => 'session-12'], 46 | ['name' => 'Session 13', 'event' => 'event-3', 'reference' => 'session-13'], 47 | ['name' => 'Session 14', 'event' => 'event-3', 'reference' => 'session-14'], 48 | ['name' => 'Session 15', 'event' => 'event-3', 'reference' => 'session-15'], 49 | ]; 50 | 51 | foreach ($data as $item) { 52 | /** @var Event $event */ 53 | $event = $this->getReference($item['event']); 54 | 55 | $session = new Session(); 56 | $session->setName($item['name']); 57 | $session->setEvent($event); 58 | $manager->persist($session); 59 | 60 | $this->addReference($item['reference'], $session); 61 | } 62 | 63 | $manager->flush(); 64 | } 65 | 66 | /** 67 | * Set loading order. 68 | * 69 | * @return int 70 | */ 71 | public function getOrder() 72 | { 73 | return 3; 74 | } 75 | } -------------------------------------------------------------------------------- /ci/integration-test/environment/src/DataFixtures/ORM/LoadTagData.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App\DataFixtures\ORM; 13 | 14 | use Doctrine\Bundle\FixturesBundle\Fixture; 15 | use Doctrine\Common\DataFixtures\OrderedFixtureInterface; 16 | use Doctrine\Persistence\ObjectManager; 17 | use App\Entity\Tag; 18 | 19 | /** 20 | * Class LoadTagData 21 | * 22 | * @author Andrey Nilov 23 | * @package Glavweb\DatagridBundle 24 | */ 25 | class LoadTagData extends Fixture implements OrderedFixtureInterface 26 | { 27 | /** 28 | * @param ObjectManager $manager 29 | */ 30 | public function load(ObjectManager $manager) 31 | { 32 | $data = [ 33 | ['name' => 'Tag 1', 'reference' => 'tag-1'], 34 | ['name' => 'Tag 2', 'reference' => 'tag-2'], 35 | ['name' => 'Tag 3', 'reference' => 'tag-3'], 36 | ['name' => 'Tag 4', 'reference' => 'tag-4'], 37 | ['name' => 'Tag 5', 'reference' => 'tag-5'], 38 | ]; 39 | 40 | foreach ($data as $item) { 41 | $tag = new Tag(); 42 | $tag->setName($item['name']); 43 | $manager->persist($tag); 44 | 45 | $this->addReference($item['reference'], $tag); 46 | } 47 | 48 | $manager->flush(); 49 | } 50 | 51 | /** 52 | * Set loading order. 53 | * 54 | * @return int 55 | */ 56 | public function getOrder() 57 | { 58 | return 1; 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /ci/integration-test/environment/src/DataSchema/AppDataSchemaExtension.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class AppDataSchemaExtension implements ExtensionInterface 17 | { 18 | /** 19 | * @return DataTransformerInterface[] 20 | */ 21 | public function getDataTransformers() 22 | { 23 | return [ 24 | 'upper' => new SimpleDataTransformer([$this, 'transformUpper']), 25 | 'concat_slug_with_year' => new SimpleDataTransformer([$this, 'transformConcatSlugWithYear']), 26 | 'has_events' => new SimpleDataTransformer([$this, 'transformHasEvents']), 27 | ]; 28 | } 29 | 30 | /** 31 | * @param $value 32 | * @param TransformEvent $event 33 | * @return string 34 | */ 35 | public function transformUpper($value, TransformEvent $event): string 36 | { 37 | return strtoupper($value); 38 | } 39 | 40 | /** 41 | * @param $value 42 | * @param TransformEvent $event 43 | * @return string 44 | */ 45 | public function transformConcatSlugWithYear($value, TransformEvent $event): string 46 | { 47 | if ($event->getClassName() !== Article::class) { 48 | throw new \RuntimeException('This transformer may be used with only instance of Article.'); 49 | } 50 | 51 | $data = $event->getData(); 52 | $publishAt = !$data['publishAt'] instanceof \DateTime ? (new \DateTime($data['publishAt'])) : $data['publishAt']; 53 | 54 | return $data['slug'] . '_' . $publishAt->format('Y'); 55 | } 56 | 57 | /** 58 | * @param $articleEvents 59 | * @param TransformEvent $event 60 | * @return bool 61 | */ 62 | public function transformHasEvents($articleEvents, TransformEvent $event): bool 63 | { 64 | if ($event->getClassName() !== Article::class) { 65 | throw new \RuntimeException('This transformer may be used with only instance of Article.'); 66 | } 67 | 68 | return $articleEvents > 0; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /ci/integration-test/environment/src/Entity/Article.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App\Entity; 13 | 14 | use Doctrine\Common\Collections\ArrayCollection; 15 | use Doctrine\ORM\Mapping as ORM; 16 | 17 | /** 18 | * Article 19 | * 20 | * @author Andrey Nilov 21 | * @package Glavweb\DatagridBundle 22 | * 23 | * @ORM\Table(name="articles") 24 | * @ORM\Entity 25 | */ 26 | class Article 27 | { 28 | /** 29 | * @var integer 30 | * 31 | * @ORM\Column(name="id", type="integer") 32 | * @ORM\Id 33 | * @ORM\GeneratedValue(strategy="AUTO") 34 | */ 35 | private $id; 36 | 37 | /** 38 | * @var string 39 | * 40 | * @ORM\Column(name="name", type="string") 41 | */ 42 | private $name; 43 | 44 | /** 45 | * @var string 46 | * 47 | * @ORM\Column(name="slug", type="string") 48 | */ 49 | private $slug; 50 | 51 | /** 52 | * @var string 53 | * 54 | * @ORM\Column(name="body", type="text") 55 | */ 56 | private $body; 57 | 58 | /** 59 | * @var int 60 | * 61 | * @ORM\Column(name="count_events", type="integer") 62 | */ 63 | private $countEvents; 64 | 65 | /** 66 | * @var boolean 67 | * 68 | * @ORM\Column(name="is_publish", type="boolean") 69 | */ 70 | private $publish; 71 | 72 | /** 73 | * @var \DateTime 74 | * 75 | * @ORM\Column(name="publish_at", type="datetime") 76 | */ 77 | private $publishAt; 78 | 79 | /** 80 | * @var ArrayCollection 81 | * 82 | * @ORM\ManyToMany(targetEntity="Event", inversedBy="articles") 83 | */ 84 | private $events; 85 | 86 | /** 87 | * Constructor 88 | */ 89 | public function __construct() 90 | { 91 | $this->events = new ArrayCollection(); 92 | } 93 | 94 | /** 95 | * Get id 96 | * 97 | * @return integer 98 | */ 99 | public function getId() 100 | { 101 | return $this->id; 102 | } 103 | 104 | /** 105 | * Set name 106 | * 107 | * @param string $name 108 | * 109 | * @return Article 110 | */ 111 | public function setName($name) 112 | { 113 | $this->name = $name; 114 | 115 | return $this; 116 | } 117 | 118 | /** 119 | * Get name 120 | * 121 | * @return string 122 | */ 123 | public function getName() 124 | { 125 | return $this->name; 126 | } 127 | 128 | /** 129 | * Set slug 130 | * 131 | * @param string $slug 132 | * 133 | * @return Article 134 | */ 135 | public function setSlug($slug) 136 | { 137 | $this->slug = $slug; 138 | 139 | return $this; 140 | } 141 | 142 | /** 143 | * Get slug 144 | * 145 | * @return string 146 | */ 147 | public function getSlug() 148 | { 149 | return $this->slug; 150 | } 151 | 152 | /** 153 | * Set body 154 | * 155 | * @param string $body 156 | * 157 | * @return Article 158 | */ 159 | public function setBody($body) 160 | { 161 | $this->body = $body; 162 | 163 | return $this; 164 | } 165 | 166 | /** 167 | * Get body 168 | * 169 | * @return string 170 | */ 171 | public function getBody() 172 | { 173 | return $this->body; 174 | } 175 | 176 | /** 177 | * @param int $countEvents 178 | */ 179 | public function setCountEvents($countEvents) 180 | { 181 | $this->countEvents = $countEvents; 182 | } 183 | 184 | /** 185 | * @return int 186 | */ 187 | public function getCountEvents() 188 | { 189 | return $this->countEvents; 190 | } 191 | 192 | /** 193 | * @param boolean $publish 194 | * 195 | * @return Article 196 | */ 197 | public function setPublish($publish) 198 | { 199 | $this->publish = $publish; 200 | 201 | return $this; 202 | } 203 | 204 | /** 205 | * @return boolean 206 | */ 207 | public function isPublish() 208 | { 209 | return $this->publish; 210 | } 211 | 212 | /** 213 | * @param \DateTime $publishAt 214 | * 215 | * @return Article 216 | */ 217 | public function setPublishAt($publishAt) 218 | { 219 | $this->publishAt = $publishAt; 220 | 221 | return $this; 222 | } 223 | 224 | /** 225 | * @return \DateTime 226 | */ 227 | public function getPublishAt() 228 | { 229 | return $this->publishAt; 230 | } 231 | 232 | /** 233 | * Add event 234 | * 235 | * @param Event $event 236 | * 237 | * @return Article 238 | */ 239 | public function addEvent(Event $event) 240 | { 241 | $this->events[] = $event; 242 | 243 | return $this; 244 | } 245 | 246 | /** 247 | * Remove event 248 | * 249 | * @param Event $event 250 | */ 251 | public function removeEvent(Event $event) 252 | { 253 | $this->events->removeElement($event); 254 | } 255 | 256 | /** 257 | * Get events 258 | * 259 | * @return ArrayCollection 260 | */ 261 | public function getEvents() 262 | { 263 | return $this->events; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /ci/integration-test/environment/src/Entity/Event.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App\Entity; 13 | 14 | use Doctrine\Common\Collections\ArrayCollection; 15 | use Doctrine\ORM\Mapping as ORM; 16 | 17 | /** 18 | * Event 19 | * 20 | * @author Andrey Nilov 21 | * @package Glavweb\DatagridBundle 22 | * 23 | * @ORM\Table(name="events") 24 | * @ORM\Entity 25 | */ 26 | class Event 27 | { 28 | /** 29 | * @var integer 30 | * 31 | * @ORM\Column(name="id", type="integer") 32 | * @ORM\Id 33 | * @ORM\GeneratedValue(strategy="AUTO") 34 | */ 35 | private $id; 36 | 37 | /** 38 | * @var string 39 | * 40 | * @ORM\Column(name="name", type="string") 41 | */ 42 | private $name; 43 | 44 | /** 45 | * @var EventGroup 46 | * 47 | * @ORM\ManyToOne(targetEntity="EventGroup", inversedBy="events") 48 | */ 49 | private $eventGroup; 50 | 51 | /** 52 | * @var ArrayCollection 53 | * 54 | * @ORM\ManyToMany(targetEntity="Tag", inversedBy="events") 55 | * @ORM\OrderBy({"id": "asc"}) 56 | */ 57 | private $tags; 58 | 59 | /** 60 | * @var ArrayCollection 61 | * 62 | * @ORM\ManyToMany(targetEntity="Article", mappedBy="events") 63 | * @ORM\OrderBy({"id": "asc"}) 64 | */ 65 | private $articles; 66 | 67 | /** 68 | * @var ArrayCollection 69 | * 70 | * @ORM\OneToMany(targetEntity="Session", mappedBy="event") 71 | * @ORM\OrderBy({"id": "asc"}) 72 | */ 73 | private $sessions; 74 | 75 | /** 76 | * @var EventDetail 77 | * 78 | * @ORM\OneToOne(targetEntity="EventDetail", inversedBy="event") 79 | */ 80 | private $eventDetail; 81 | 82 | /** 83 | * Constructor 84 | */ 85 | public function __construct() 86 | { 87 | $this->tags = new ArrayCollection(); 88 | $this->articles = new ArrayCollection(); 89 | $this->sessions = new ArrayCollection(); 90 | } 91 | 92 | /** 93 | * Get id 94 | * 95 | * @return integer 96 | */ 97 | public function getId() 98 | { 99 | return $this->id; 100 | } 101 | 102 | /** 103 | * Set name 104 | * 105 | * @param string $name 106 | * 107 | * @return Event 108 | */ 109 | public function setName($name) 110 | { 111 | $this->name = $name; 112 | 113 | return $this; 114 | } 115 | 116 | /** 117 | * Get name 118 | * 119 | * @return string 120 | */ 121 | public function getName() 122 | { 123 | return $this->name; 124 | } 125 | 126 | /** 127 | * Set eventGroup 128 | * 129 | * @param EventGroup $eventGroup 130 | * 131 | * @return Event 132 | */ 133 | public function setEventGroup(EventGroup $eventGroup = null) 134 | { 135 | $this->eventGroup = $eventGroup; 136 | 137 | return $this; 138 | } 139 | 140 | /** 141 | * Get eventGroup 142 | * 143 | * @return EventGroup 144 | */ 145 | public function getEventGroup() 146 | { 147 | return $this->eventGroup; 148 | } 149 | 150 | /** 151 | * Add tag 152 | * 153 | * @param Tag $tag 154 | * 155 | * @return Event 156 | */ 157 | public function addTag(Tag $tag) 158 | { 159 | $this->tags[] = $tag; 160 | 161 | return $this; 162 | } 163 | 164 | /** 165 | * Remove tag 166 | * 167 | * @param Tag $tag 168 | */ 169 | public function removeTag(Tag $tag) 170 | { 171 | $this->tags->removeElement($tag); 172 | } 173 | 174 | /** 175 | * Get tags 176 | * 177 | * @return ArrayCollection 178 | */ 179 | public function getTags() 180 | { 181 | return $this->tags; 182 | } 183 | 184 | /** 185 | * Add article 186 | * 187 | * @param Article $article 188 | * 189 | * @return Event 190 | */ 191 | public function addArticle(Article $article) 192 | { 193 | $this->articles[] = $article; 194 | 195 | return $this; 196 | } 197 | 198 | /** 199 | * Remove article 200 | * 201 | * @param Article $article 202 | */ 203 | public function removeArticle(Article $article) 204 | { 205 | $this->articles->removeElement($article); 206 | } 207 | 208 | /** 209 | * Get articles 210 | * 211 | * @return ArrayCollection 212 | */ 213 | public function getArticles() 214 | { 215 | return $this->articles; 216 | } 217 | 218 | /** 219 | * Add session 220 | * 221 | * @param Session $session 222 | * 223 | * @return Event 224 | */ 225 | public function addSession(Session $session) 226 | { 227 | $this->sessions[] = $session; 228 | 229 | return $this; 230 | } 231 | 232 | /** 233 | * Remove session 234 | * 235 | * @param Session $session 236 | */ 237 | public function removeSession(Session $session) 238 | { 239 | $this->sessions->removeElement($session); 240 | } 241 | 242 | /** 243 | * Get sessions 244 | * 245 | * @return ArrayCollection 246 | */ 247 | public function getSessions() 248 | { 249 | return $this->sessions; 250 | } 251 | 252 | /** 253 | * Set eventDetail 254 | * 255 | * @param EventDetail $eventDetail 256 | * 257 | * @return Event 258 | */ 259 | public function setEventDetail(EventDetail $eventDetail = null) 260 | { 261 | $this->eventDetail = $eventDetail; 262 | 263 | return $this; 264 | } 265 | 266 | /** 267 | * Get eventDetail 268 | * 269 | * @return EventDetail 270 | */ 271 | public function getEventDetail() 272 | { 273 | return $this->eventDetail; 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /ci/integration-test/environment/src/Entity/EventDetail.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App\Entity; 13 | 14 | use Doctrine\ORM\Mapping as ORM; 15 | 16 | /** 17 | * EventDetail 18 | * 19 | * @author Andrey Nilov 20 | * @package Glavweb\DatagridBundle 21 | * 22 | * @ORM\Table(name="event_details") 23 | * @ORM\Entity 24 | */ 25 | class EventDetail 26 | { 27 | /** 28 | * @var integer 29 | * 30 | * @ORM\Column(name="id", type="integer") 31 | * @ORM\Id 32 | * @ORM\GeneratedValue(strategy="AUTO") 33 | */ 34 | private $id; 35 | 36 | /** 37 | * @var string 38 | * 39 | * @ORM\Column(name="body", type="text") 40 | */ 41 | private $body; 42 | 43 | /** 44 | * @var Event 45 | * 46 | * @ORM\OneToOne(targetEntity="Event", mappedBy="eventDetail") 47 | */ 48 | private $event; 49 | 50 | /** 51 | * Get id 52 | * 53 | * @return integer 54 | */ 55 | public function getId() 56 | { 57 | return $this->id; 58 | } 59 | 60 | /** 61 | * Set body 62 | * 63 | * @param string $body 64 | * 65 | * @return EventDetail 66 | */ 67 | public function setBody($body) 68 | { 69 | $this->body = $body; 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * Get body 76 | * 77 | * @return string 78 | */ 79 | public function getBody() 80 | { 81 | return $this->body; 82 | } 83 | 84 | /** 85 | * Set event 86 | * 87 | * @param Event $event 88 | * 89 | * @return EventDetail 90 | */ 91 | public function setEvent(Event $event = null) 92 | { 93 | $this->event = $event; 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * Get event 100 | * 101 | * @return Event 102 | */ 103 | public function getEvent() 104 | { 105 | return $this->event; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /ci/integration-test/environment/src/Entity/EventGroup.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App\Entity; 13 | 14 | use Doctrine\Common\Collections\ArrayCollection; 15 | use Doctrine\ORM\Mapping as ORM; 16 | 17 | /** 18 | * EventGroup 19 | * 20 | * @author Andrey Nilov 21 | * @package Glavweb\DatagridBundle 22 | * 23 | * @ORM\Table(name="event_groups") 24 | * @ORM\Entity 25 | */ 26 | class EventGroup 27 | { 28 | /** 29 | * @var integer 30 | * 31 | * @ORM\Column(name="id", type="integer") 32 | * @ORM\Id 33 | * @ORM\GeneratedValue(strategy="AUTO") 34 | */ 35 | private $id; 36 | 37 | /** 38 | * @var string 39 | * 40 | * @ORM\Column(name="name", type="string") 41 | */ 42 | private $name; 43 | 44 | /** 45 | * @var ArrayCollection 46 | * 47 | * @ORM\OneToMany(targetEntity="Event", mappedBy="eventGroup") 48 | */ 49 | private $events; 50 | 51 | /** 52 | * Constructor 53 | */ 54 | public function __construct() 55 | { 56 | $this->events = new ArrayCollection(); 57 | } 58 | 59 | /** 60 | * Get id 61 | * 62 | * @return integer 63 | */ 64 | public function getId() 65 | { 66 | return $this->id; 67 | } 68 | 69 | /** 70 | * Set name 71 | * 72 | * @param string $name 73 | * 74 | * @return EventGroup 75 | */ 76 | public function setName($name) 77 | { 78 | $this->name = $name; 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * Get name 85 | * 86 | * @return string 87 | */ 88 | public function getName() 89 | { 90 | return $this->name; 91 | } 92 | 93 | /** 94 | * Add event 95 | * 96 | * @param Event $event 97 | * 98 | * @return EventGroup 99 | */ 100 | public function addEvent(Event $event) 101 | { 102 | $this->events[] = $event; 103 | 104 | return $this; 105 | } 106 | 107 | /** 108 | * Remove event 109 | * 110 | * @param Event $event 111 | */ 112 | public function removeEvent(Event $event) 113 | { 114 | $this->events->removeElement($event); 115 | } 116 | 117 | /** 118 | * Get events 119 | * 120 | * @return ArrayCollection 121 | */ 122 | public function getEvents() 123 | { 124 | return $this->events; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /ci/integration-test/environment/src/Entity/Session.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App\Entity; 13 | 14 | use Doctrine\ORM\Mapping as ORM; 15 | 16 | /** 17 | * Session 18 | * 19 | * @author Andrey Nilov 20 | * @package Glavweb\DatagridBundle 21 | * 22 | * @ORM\Table(name="event_sessions") 23 | * @ORM\Entity 24 | */ 25 | class Session 26 | { 27 | /** 28 | * @var integer 29 | * 30 | * @ORM\Column(name="id", type="integer", options={"comment": "ID"}) 31 | * @ORM\Id 32 | * @ORM\GeneratedValue(strategy="AUTO") 33 | */ 34 | private $id; 35 | 36 | /** 37 | * @var string 38 | * 39 | * @ORM\Column(name="name", type="string") 40 | */ 41 | private $name; 42 | 43 | /** 44 | * @var Event 45 | * 46 | * @ORM\ManyToOne(targetEntity="Event", inversedBy="sessions") 47 | */ 48 | private $event; 49 | 50 | /** 51 | * Get id 52 | * 53 | * @return integer 54 | */ 55 | public function getId() 56 | { 57 | return $this->id; 58 | } 59 | 60 | /** 61 | * Set name 62 | * 63 | * @param string $name 64 | * 65 | * @return Session 66 | */ 67 | public function setName($name) 68 | { 69 | $this->name = $name; 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * Get name 76 | * 77 | * @return string 78 | */ 79 | public function getName() 80 | { 81 | return $this->name; 82 | } 83 | 84 | /** 85 | * Set event 86 | * 87 | * @param Event $event 88 | * 89 | * @return Session 90 | */ 91 | public function setEvent(Event $event = null) 92 | { 93 | $this->event = $event; 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * Get event 100 | * 101 | * @return Event 102 | */ 103 | public function getEvent() 104 | { 105 | return $this->event; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /ci/integration-test/environment/src/Entity/Tag.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App\Entity; 13 | 14 | use Doctrine\Common\Collections\ArrayCollection; 15 | use Doctrine\ORM\Mapping as ORM; 16 | 17 | /** 18 | * Tag 19 | * 20 | * @author Andrey Nilov 21 | * @package Glavweb\DatagridBundle 22 | * 23 | * @ORM\Table(name="tags") 24 | * @ORM\Entity 25 | */ 26 | class Tag 27 | { 28 | /** 29 | * @var integer 30 | * 31 | * @ORM\Column(name="id", type="integer", options={"comment": "ID"}) 32 | * @ORM\Id 33 | * @ORM\GeneratedValue(strategy="AUTO") 34 | */ 35 | private $id; 36 | 37 | /** 38 | * @var string 39 | * 40 | * @ORM\Column(name="name", type="string", length=255, options={"comment": "Название"}) 41 | */ 42 | private $name; 43 | 44 | /** 45 | * @var ArrayCollection 46 | * 47 | * @ORM\ManyToMany(targetEntity="Event", mappedBy="tags") 48 | */ 49 | private $events; 50 | 51 | /** 52 | * Constructor 53 | */ 54 | public function __construct() 55 | { 56 | $this->events = new ArrayCollection(); 57 | } 58 | 59 | /** 60 | * Get id 61 | * 62 | * @return integer 63 | */ 64 | public function getId() 65 | { 66 | return $this->id; 67 | } 68 | 69 | /** 70 | * Set name 71 | * 72 | * @param string $name 73 | * 74 | * @return Tag 75 | */ 76 | public function setName($name) 77 | { 78 | $this->name = $name; 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * Get name 85 | * 86 | * @return string 87 | */ 88 | public function getName() 89 | { 90 | return $this->name; 91 | } 92 | 93 | /** 94 | * Add event 95 | * 96 | * @param Event $event 97 | * 98 | * @return Tag 99 | */ 100 | public function addEvent(Event $event) 101 | { 102 | $this->events[] = $event; 103 | 104 | return $this; 105 | } 106 | 107 | /** 108 | * Remove event 109 | * 110 | * @param Event $event 111 | */ 112 | public function removeEvent(Event $event) 113 | { 114 | $this->events->removeElement($event); 115 | } 116 | 117 | /** 118 | * Get events 119 | * 120 | * @return ArrayCollection 121 | */ 122 | public function getEvents() 123 | { 124 | return $this->events; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /ci/integration-test/environment/tests/Datagrid/Doctrine/DatagridOrmTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App\Tests\Datagrid\Doctrine; 13 | 14 | use Glavweb\DatagridBundle\Factory\DatagridFactoryInterface; 15 | 16 | /** 17 | * Class DatagridOrmTest 18 | * 19 | * @package Glavweb\DatagridBundle 20 | * @author Andrey Nilov 21 | */ 22 | class DatagridOrmTest extends DatagridNativeTest 23 | { 24 | /** 25 | * @return DatagridFactoryInterface 26 | */ 27 | protected function getDatagridFactory(): DatagridFactoryInterface 28 | { 29 | /** @var DatagridFactoryInterface $factory */ 30 | $factory = self::$container->get('glavweb_datagrid.orm_factory'); 31 | 32 | return $factory; 33 | } 34 | 35 | /** 36 | * @return array 37 | */ 38 | public function dataTestDecodeWithQuerySelects() 39 | { 40 | return [ 41 | [ 42 | 'dataSchemaFile' => 'test_decode_with_query_selects_orm.schema.yml' 43 | ] 44 | ]; 45 | } 46 | } -------------------------------------------------------------------------------- /ci/integration-test/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | set -e 4 | 5 | cd `dirname "$0"` 6 | 7 | export COMPOSE_PROJECT_NAME=glavweb-datagrid-bundle-test 8 | export BUNDLE_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) 9 | 10 | docker-compose rm --force --stop 11 | docker-compose build --build-arg BUNDLE_VERSION=$BUNDLE_VERSION 12 | docker-compose up --no-start 13 | set +e 14 | docker-compose run --rm php ../scripts/run.sh 15 | set -e 16 | docker-compose rm --force --stop -------------------------------------------------------------------------------- /ci/integration-test/scripts/copy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | 4 | cd .. 5 | 6 | rm -rf build/* 7 | cp -r app/. build -------------------------------------------------------------------------------- /ci/integration-test/scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | set -e 4 | 5 | 6 | #../scripts/copy.sh 7 | 8 | composer require glavweb/datagrid-bundle orm-fixtures 9 | 10 | rm src/DataFixtures/AppFixtures.php 11 | 12 | ../scripts/copy.sh 13 | 14 | php bin/console --env test about 15 | 16 | php bin/console --env test -n doctrine:database:create 17 | php bin/console --env test -n doctrine:schema:create 18 | php bin/console --env test -n doctrine:fixtures:load 19 | 20 | php bin/phpunit --stop-on-error 21 | 22 | #../scripts/copy.sh 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glavweb/datagrid-bundle", 3 | "description": "GLAVWEB DatagridBundle", 4 | "authors": [ 5 | { 6 | "name": "nilov", 7 | "email": "nilov@glavweb.ru", 8 | "homepage": "http://glavweb.ru" 9 | }, 10 | { 11 | "name": "GLAVWEB Community", 12 | "homepage": "https://github.com/glavweb/GlavwebDatagridBundle/contributors" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=7.2.5", 17 | "symfony/config": "^4.0|^5.0|^6.0|^7.0", 18 | "symfony/dependency-injection": "^4.0|^5.0|^6.0|^7.0", 19 | "symfony/yaml": "^4.0|^5.0|^6.0|^7.0", 20 | "doctrine/orm": "^2.3|^3.3", 21 | "doctrine/doctrine-bundle": "^2.0", 22 | "glavweb/data-schema-bundle": "^2.6.1", 23 | "ext-json": "*" 24 | }, 25 | "require-dev": { 26 | "symfony/phpunit-bridge": "^3.3", 27 | "phpunit/phpunit": "^6.4" 28 | }, 29 | "autoload": { 30 | "exclude-from-classmap": ["ci", "build"], 31 | "psr-4": { "Glavweb\\DatagridBundle\\": "" } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { "Glavweb\\DatagridBundle\\Tests\\": "" } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Tests 23 | 24 | 25 | 26 | 27 | 28 | src 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 42 | 43 | --------------------------------------------------------------------------------