├── LICENSE ├── README.md ├── UPGRADE.md ├── composer.json └── src ├── AbstractDatagridExtension.php ├── BaseView.php ├── Column ├── AbstractType.php ├── AbstractTypeExtension.php ├── CellView.php ├── Column.php ├── ColumnInterface.php ├── ColumnTypeExtensionInterface.php ├── ColumnTypeInterface.php ├── ColumnTypeRegistry.php ├── ColumnTypeRegistryInterface.php ├── CompoundColumn.php ├── HeaderView.php ├── ResolvedColumnType.php ├── ResolvedColumnTypeFactory.php ├── ResolvedColumnTypeFactoryInterface.php └── ResolvedColumnTypeInterface.php ├── DataTransformerInterface.php ├── Datagrid.php ├── DatagridBuilder.php ├── DatagridBuilderInterface.php ├── DatagridConfiguratorInterface.php ├── DatagridExtensionInterface.php ├── DatagridFactory.php ├── DatagridFactoryInterface.php ├── DatagridInterface.php ├── DatagridRegistry.php ├── DatagridRegistryInterface.php ├── DatagridRowView.php ├── DatagridView.php ├── Datagrids.php ├── Exception ├── BadMethodCallException.php ├── DataMappingException.php ├── DataProviderException.php ├── DatagridColumnException.php ├── DatagridException.php ├── ExceptionInterface.php ├── InvalidArgumentException.php ├── InvalidConfigurationException.php ├── LogicException.php ├── TransformationFailedException.php ├── UnexpectedTypeException.php └── UnknownColumnException.php ├── Extension └── Core │ ├── CoreExtension.php │ ├── DataTransformer │ ├── BaseDateTimeTransformer.php │ ├── ChainTransformer.php │ ├── DateTimeToLocalizedStringTransformer.php │ ├── EmptyValueTransformer.php │ ├── IntegerToLocalizedStringTransformer.php │ ├── MoneyToLocalizedStringTransformer.php │ ├── NumberToLocalizedStringTransformer.php │ ├── StringToDateTimeTransformer.php │ ├── TimestampToDateTimeTransformer.php │ └── ValueFormatTransformer.php │ └── Type │ ├── ActionType.php │ ├── BaseType.php │ ├── BatchType.php │ ├── BooleanType.php │ ├── ColumnType.php │ ├── CompoundColumnType.php │ ├── DateTimeType.php │ ├── MoneyType.php │ ├── NumberType.php │ └── TextType.php ├── PreloadedExtension.php ├── Test ├── ColumnTypeTestCase.php ├── DatagridIntegrationTestCase.php ├── DatagridPerformanceTestCase.php └── MockTestCase.php └── Util ├── CompoundColumnBuilder.php ├── CompoundColumnBuilderInterface.php ├── DatagridFactoryBuilder.php └── StringUtil.php /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2017 Sebastiaan Stok 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RollerworksDatagrid 2 | =================== 3 | 4 | RollerworksDatagrid provides a powerful datagrid system for your PHP applications. 5 | 6 | Displaying an objects list is one of the most common tasks in web applications 7 | and probably the easiest one. So how can this library help you? 8 | 9 | The Datagrid system makes the styling and transforming of your data more uniform 10 | and easier to use. Secondly the system can take care of specific (styling) issues 11 | like sorting and paginating. All without duplicating code or degrading performance. 12 | 13 | ## Features 14 | 15 | RollerworksDatagrid provides you with all features needed, including: 16 | 17 | * An Advanced column type system for uniform data transformation and styling 18 | in your datagrids. 19 | * Auto mapping of data to the datagrid. 20 | * Support for any data source (PHP array or any object implementing `\Traversable`). 21 | * (Optional, and coming soon) search/filter using [RollerworksSearch]. 22 | * (Optional, and coming soon) Integrated Paginating using [Pagerfanta](https://github.com/whiteoctober/Pagerfanta) 23 | 24 | > **Note:** Passing a `Pagerfanta` object as data source _does already work_ 25 | > due to the `IteratorAggregate` implementation. The integration bridge however 26 | > will make the rendering more uniform for datagrids. 27 | 28 | ## Framework integration 29 | 30 | RollerworksDatagrid can be used with any Framework of your choice, but for the best 31 | possible experience use the provided framework integration plug-ins. 32 | 33 | * [Symfony Bundle](https://github.com/rollerworks/datagrid-bundle) 34 | * ZendFramework2 Plugin (coming soon) 35 | * Silex Plugin (coming soon) 36 | 37 | Your favorite framework not listed? No problem, see the [Contributing Guidelines] 38 | on how you can help! 39 | 40 | ## Installation and usage 41 | 42 | *Please ignore the instructions below if your use a framework integration.* 43 | [Read the Documentation for master] for complete instructions and information. 44 | 45 | Install the RollerworksDatagrid "core" library using [Composer]: 46 | 47 | ```bash 48 | $ composer install rollerworks/datagrid 49 | ``` 50 | 51 | And create the `DatagridFactory` to get started: 52 | 53 | ```php 54 | use Rollerworks\Component\Datagrid\Datagrids; 55 | use Rollerworks\Component\Datagrid\Extension\Core\Type as ColumnType; 56 | 57 | $datagridFactory = Datagrids::createDatagridFactory(); 58 | 59 | $datagrid = $datagridFactory->createDatagridBuilder() 60 | ->add('id', ColumnType\NumberType::class) 61 | ->add('username', ColumnType\TextType::class) 62 | ->add('registered_on', ColumnType\DateTimeType::class) 63 | ->add('enabled', ColumnType\BooleanType::class, ['true_value' => 'Yes', 'false_value' => 'No']) 64 | ->getDatagrid('users_datagrid') 65 | ; 66 | 67 | // Now set the data for the grid, this cannot be changed afterwards. 68 | $datagrid->setData([ 69 | ['id' => 1, 'username' => 'sstok', 'registered_on' => new \DateTime('2017-01-12 14:26:00 CET'), 'enabled' => true], 70 | ['id' => 2, 'username' => 'doctorw', 'registered_on' => new \DateTime('1980-04-12 09:26:00 CET'), 'enabled' => false], 71 | // etc... 72 | ]); 73 | 74 | // Almost done, the datagrid needs to be rendered, see bellow. 75 | ``` 76 | 77 | ### Rendering 78 | 79 | The core package however doesn't provide an implementation for this, 80 | you are free to use any compatible template engine you wish. 81 | 82 | This example uses the [TwigRendererEngine](https://github.com/rollerworks/datagrid-twig) 83 | (which needs to be installed separately). 84 | 85 | ```php 86 | use Rollerworks\Component\Datagrid\Twig\Extension\DatagridExtension; 87 | use Rollerworks\Component\Datagrid\Twig\Renderer\TwigRenderer; 88 | use Rollerworks\Component\Datagrid\Twig\Renderer\TwigRendererEngine; 89 | 90 | // Provide the path to the base theme. 91 | $loader = new \Twig_Loader_Filesystem([...]); 92 | 93 | $environment = new \Twig_Environment($loader); 94 | $environment->addExtension(new DatagridExtension()); 95 | $environment->addRuntimeLoader(new \Twig_FactoryRuntimeLoader([TwigRenderer::class => function () uses ($environment) { 96 | // The second argument are filenames of datagrid themes. 97 | $rendererEngine = new TwigRendererEngine($environment, ['datagrid.html.twig']); 98 | 99 | return new TwigRenderer($rendererEngine); 100 | }])); 101 | 102 | $environment->render('my_page.html.twig', ['datagrid' => $datagrid->createView()]); 103 | ``` 104 | 105 | And in the `my_page.html.twig` twig Template simple use: 106 | 107 | ```jinja 108 | {{ rollerworks_datagrid(datagrid) }} 109 | ``` 110 | 111 | That's it! Your datagrid is now rendered, but not only that! Whenever you use an 112 | advanced technique like search you only need this much code in your template. 113 | 114 | ## Resources 115 | 116 | * [Read the Documentation for master] 117 | * RollerworksDatagrid is maintained under the [Semantic Versioning guidelines](http://semver.org/) 118 | 119 | ## Who is behind RollerworksDatagrid? 120 | 121 | RollerworksDatagrid is brought to you by [Sebastiaan Stok](https://github.com/sstok). 122 | 123 | ## License 124 | 125 | RollerworksDatagrid is released under the [MIT license](LICENSE). 126 | 127 | The types and extensions are largely inspired on the Symfony Form Component, 128 | and contain a big amount of code from the Symfony project. 129 | 130 | ## Support 131 | 132 | [Join the chat] or use the issue tracker if your question is to complex for quick support. 133 | 134 | > **Note:** RollerworksDatagrid doesn't have a support forum at the moment, if you know 135 | > a good free service let us know by opening an issue :+1: 136 | 137 | ## Contributing 138 | 139 | This is an open source project. If you'd like to contribute, 140 | please read the [Contributing Guidelines]. If you're submitting 141 | a pull request, please follow the guidelines in the [Submitting a Patch] section. 142 | 143 | [RollerworksSearch]: https://github.com/rollerworks/search 144 | [Join the chat at https://gitter.im/rollerworks/datagrid](https://gitter.im/rollerworks/datagrid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 145 | [Composer]: https://getcomposer.org/doc/00-intro.md 146 | [Contributing Guidelines]: https://github.com/rollerworks/contributing 147 | [Submitting a Patch]: https://contributing.readthedocs.org/en/latest/code/patches.html 148 | [Read the Documentation for master]: http://rollerworksdatagrid.readthedocs.org/en/latest/ 149 | [Join the chat]: https://gitter.im/rollerworks/datagrid 150 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | UPGRADE 2 | ======= 3 | 4 | ## Upgrade FROM 0.10 to 0.11 5 | 6 | * All view classes now extend the `BaseView` class. 7 | 8 | * The `attributes` property of the view classes is renamed 9 | to `vars`. 10 | 11 | * The first argument of `ColumnInterface::createCellView` 12 | now expects a `HeaderView` instead of `DatagridView`. 13 | 14 | * The second argument of `ResolvedColumnTypeInterface::createCellView` 15 | now expects a `HeaderView` instead of `DatagridView`. 16 | 17 | * The `DatagridView` no longer initializes the headers and rows 18 | within the constructor. You must call `DatagridView::init` with 19 | the actual datagrid instance. 20 | 21 | **Note:** When you use the (default) `Datagrid` class, this is already 22 | done for you. 23 | 24 | ## Upgrade FROM 0.9 to 0.10 25 | 26 | **Support for PHP 5.5 is dropped, you need at least PHP 7.0.** 27 | 28 | * All classes and interfaces now (whenever possible) declare type-hints *and* return types. 29 | 30 | *As this list quite extensive, not all affected classes are listed here.* 31 | The simplest way to see if your custom implementation is affected is running your tests 32 | and or use your IDE's code analyzer to check for incompatibility's. 33 | 34 | See also: http://php.net/manual/en/functions.returning-values.php#functions.returning-values.type-declaration 35 | 36 | * Datagrid and ColumnType extensions now must be defined with a compatible return type. 37 | 38 | **Note:** `ColumnTypeInterface::getParent()` has no return type as this value can be null. 39 | 40 | **AbstractDatagridExtension** 41 | 42 | Before: 43 | 44 | ```php 45 | class MyType extends AbstractType 46 | { 47 | public function getBlockPrefix() 48 | { 49 | return ...; 50 | } 51 | } 52 | 53 | class MyExtension extends AbstractDatagridExtension 54 | { 55 | protected function loadTypes() 56 | { 57 | return [...]; 58 | } 59 | 60 | protected function loadTypesExtensions() 61 | { 62 | return [...]; 63 | } 64 | } 65 | 66 | class MyTypeExtension extends AbstractTypeExtension 67 | { 68 | public function getExtendedType() 69 | { 70 | ... 71 | } 72 | } 73 | ``` 74 | 75 | After: 76 | 77 | ```php 78 | class MyType extends AbstractType 79 | { 80 | public function getBlockPrefix(): string 81 | { 82 | return ...; 83 | } 84 | } 85 | 86 | class MyExtension extends AbstractDatagridExtension 87 | { 88 | protected function loadTypes(): array 89 | { 90 | return [...]; 91 | } 92 | 93 | protected function loadTypesExtensions(): array 94 | { 95 | return [...]; 96 | } 97 | } 98 | 99 | class MyTypeExtension extends AbstractTypeExtension 100 | { 101 | public function getExtendedType(): string 102 | { 103 | ... 104 | } 105 | } 106 | ``` 107 | 108 | * The `type` property of the `CellView` is removed, this was already no longer populated. 109 | 110 | * The purpose of the `DatagridFactoryInterface::createDatagrid` method changed to create 111 | a new Datagrid using a DatagridConfigurator which is loaded using a registry. 112 | 113 | The default registry's implementation registers Configurators lazily using closures 114 | and allows to load configurators using there FQCN (similar to ColumnType's). 115 | 116 | ### View transformers 117 | 118 | * Adding multiple view transformers to a column is removed, a column can now only have one 119 | view transformer. 120 | 121 | Use the new `Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\ChainTransformer` 122 | to chain multiple transformers. 123 | 124 | * A column with no transformers will pass the value as-is, scalars are no automatically 125 | longer casted to a string. 126 | 127 | * The `ColumnInterface::addViewTransformers` method is renamed to `ColumnInterface::setViewTransformer`. 128 | 129 | * The `ColumnInterface::getViewTransformers` method is renamed to `ColumnInterface::getViewTransformer`. 130 | 131 | * The `ColumnInterface::resetViewTransformers` method is removed, use `setViewTransformer` with `null` 132 | to remove the configured view transformer. 133 | 134 | * The `ResolvedColumnType::normToView` method is removed. 135 | 136 | ### CompoundColumn handling 137 | 138 | The CompoundColumn type has changed to allow for better relationship handling. 139 | **Creating a CompoundColumn without the Builder is discouraged, the DatagridBuilder provides an 140 | powerful and developer friendly way to create and register a CompoundColumn.** 141 | 142 | * The the "default" `ResolvedColumnType` will generate a `CompoundColumn` when the 143 | type is `CompoundColumnType` or when the type is a child of `CompoundColumn`. 144 | 145 | * Child Columns of a CompoundColumn must be registered after the CompoundColumn is created. 146 | Each child Column must have an 'parent_column' option with the value being the `CompoundColumn` object. 147 | 148 | **Note**: For performance reasons the instance value of the option is not validated when setting. 149 | 150 | * The `columns` option is removed. Use the `CompoundColumn::setColumn()` and `CompoundColumn::getColumns`. 151 | 152 | ### Core extensions 153 | 154 | * The `currency_field` and `input_field` options are removed from the `MoneyType`. 155 | 156 | * The `MoneyType` changed in the way input values are transformed: 157 | 158 | * When the value is a string without currency it's transformed with the `currency` option. 159 | * When the value is a string with a currency, eg. `EUR 12.00` it's transformed with 'EUR' currency. 160 | * When the value is an array, the keys `currency` and `amount` are expected to be present. 161 | Currency can be `null`, then the value of the `currency` option is used. 162 | 163 | * The first argument of `IntegerToLocalizedStringTransformer::__construct()` is removed. 164 | Precision is not used with integers, and keeping this argument only causes confusion. 165 | 166 | * The class `ColumnOrderExtension` and `CompoundColumnOrderExtension`. 167 | 168 | * The `data_provider` option of the `ColumnType` no longer supports any callable 169 | but requires a `Closure` or `Symfony\Component\PropertyAccess\PropertyPath` object, or a string 170 | with a valid property-path. 171 | 172 | * The `ArrayToDateTimeTransformer` is removed, `DateTimeType` now only accepts, 173 | a `DateTime` compatible object, string in a PHP date-supported format, or timestamp. 174 | 175 | And a minor bug with the `date_format` and `time_format` options was fixed, both now 176 | only accept an integer and no longer fallback to the default. 177 | 178 | * The `ColumnType` and `CompoundColumnType` now define the 'preferred' template blocks. 179 | Together with a small addition of other data, like attributes and (label) translator domain. 180 | 181 | Whenever using a Template engine to render the Datagrid the template block-names 182 | must be honored. They are stored only in the `HeaderView`. 183 | 184 | ### DatagridBuilder 185 | 186 | * The `DatagridBuilder` now allows re-usage of the Builder instance. 187 | Each call to `getDatagrid` will produce an new `Datagrid` instance. 188 | 189 | * The name of the datagrid must now be passed when calling `getDatagrid` 190 | and not as of the Constructor. *Duplicate usage of a datagrid name is not validated.* 191 | 192 | * The `add` method no longer accepts a Column object, use the new method `set` method instead. 193 | *This was needed to make strict type-hints possible.* 194 | 195 | * The `setDatagridViewBuilder` and `getDatagridViewBuilder` methods are added to 196 | to the `DatagridBuilderInterface`. 197 | 198 | * The `get` method now always returns an `ColumnInterface` instance. 199 | 200 | * Calling `DatagridBuilder::getDatagrid()` will re-use the resolved Column instance for 201 | all Datagrid builds. 202 | 203 | * The `createCompound(string $name, array $options = [], string $type = null): CompoundColumnBuilderInterface` method is 204 | added to the `DatagridBuilderInterface`. 205 | 206 | ## Upgrade FROM 0.8 to 0.9 207 | 208 | * The `Rollerworks\Component\Datagrid\Column\HeaderView` and `Rollerworks\Component\Datagrid\DatagridView` 209 | no longer implement `ArrayAccess` interface. 210 | 211 | **Note:** This is the last version of the Rollerworks Datagrid that supports PHP 5.5. All feature releases, will 212 | require PHP 7.0 at minimum. 213 | 214 | ## Upgrade FROM 0.7 to 0.8 215 | 216 | This version contains some major BC breaking changes and introduces improvements 217 | and clean-ups. 218 | 219 | * Data binding support is removed. Instead you need process any data before setting data 220 | on the Datagrid. 221 | 222 | All related methods and constants are removed. 223 | 224 | * Support for Symfony 2.3 was dropped, the options-resolver requires at 225 | minimum Symfony 2.7 now. Symfony 3 is now allowed to be installed, and 226 | will be used unless any of your composer.json packages restricts this version. 227 | 228 | * Data passed to `Datagrid::setData()` must be an `array` or `Traversable` object, 229 | pre/post data filtering is no longer supported. 230 | 231 | * The `DatagridEvents` class is removed as no events are dispatched anymore. 232 | 233 | * Data can only be set once on a datagrid, calling `Datagrid::setData()` twice will throw 234 | an exception. 235 | 236 | * The `Datagrid` class no longer allows to change the registered columns 237 | (all must be provided in the constructor). 238 | 239 | It's advised to use `DatagridBuilder` if you need to allow changing the columns. 240 | 241 | ### Columns 242 | 243 | * The `Rollerworks\Component\Datagrid\Tests\Extension\Core\ColumnType` namespace is 244 | renamed to `Rollerworks\Component\Datagrid\Tests\Extension\Core\Type`. 245 | 246 | * The `Rollerworks\Component\Datagrid\Tests\Extension\Core\ColumnTypeExtension` namespace is 247 | renamed to `Rollerworks\Component\Datagrid\Tests\Extension\Core\TypeExtension`. 248 | 249 | * Class `Rollerworks\Component\Datagrid\Column\AbstractColumnTypeExtension` is renamed to 250 | `Rollerworks\Component\Datagrid\Column\AbstractTypeExtension`. 251 | 252 | * Class `Rollerworks\Component\Datagrid\Column\AbstractColumnType` is renamed to 253 | `Rollerworks\Component\Datagrid\Column\AbstractType`. 254 | 255 | * Methods of the `Rollerworks\Component\Datagrid\AbstractDatagridExtension` class and 256 | `Rollerworks\Component\Datagrid\DatagridExtensionInterface` no longer contain the word `Column`. 257 | For example `getColumnType` becomes to `getType`, `loadColumnTypes` becomes `loadTypes`. 258 | 259 | *The reason behind this is redundancy, there are no other types in the datagrid then column types.* 260 | 261 | * A Column instance is no longer linked to a Datagrid instance. 262 | You can still use the Datagrid information when building the Header and Cell view of a column. 263 | 264 | This was done to remove the circular dependency between the datagrid and it's columns, 265 | making it possible to create a Datagrid instance as an immutable object. 266 | 267 | * A Column will no longer dispatch any events. The EventDispatcher requirement is removed. 268 | 269 | * Type names were removed. Instead of referencing types by name, you should reference 270 | them by their fully-qualified class name (FQCN) instead. With PHP 5.5 or later, you can 271 | use the "class" constant for that: 272 | 273 | Before: 274 | 275 | ```php 276 | $datagridBuilder->add('name', 'name', ['label' => 'Name']); 277 | ``` 278 | 279 | After: 280 | 281 | ```php 282 | use Rollerworks\Component\Datagrid\Extension\Core\Type\TextType; 283 | 284 | $datagridBuilder->add('name', TextType::class, ['label' => 'Name']); 285 | ``` 286 | 287 | As a further consequence, the method `ColumnTypeInterface::getName()` was 288 | removed. You should remove this method from your types. 289 | 290 | If you want to customize the block prefix of a type in Twig, you should now 291 | implement `ColumnTypeInterface::getBlockPrefix()` instead: 292 | 293 | Before: 294 | 295 | ```php 296 | class UserProfileType extends AbstractColumnType 297 | { 298 | public function getName() 299 | { 300 | return 'profile'; 301 | } 302 | } 303 | ``` 304 | 305 | After: 306 | 307 | ```php 308 | class UserProfileType extends AbstractType 309 | { 310 | public function getBlockPrefix() 311 | { 312 | return 'profile'; 313 | } 314 | } 315 | ``` 316 | 317 | If you don't customize `getBlockPrefix()`, it defaults to the class name 318 | without "Type" suffix in underscore notation (here: "user_profile"). 319 | 320 | Type extension should return the fully-qualified class name of the extended 321 | type from `TypeExtensionInterface::getExtendedType()` now. 322 | 323 | Before: 324 | 325 | ```php 326 | class MyTypeExtension extends AbstractColumnTypeExtension 327 | { 328 | public function getExtendedType() 329 | { 330 | return 'column'; 331 | } 332 | } 333 | ``` 334 | 335 | After: 336 | 337 | ```php 338 | use Rollerworks\Component\Datagrid\Extension\Core\Type\CoumnType; 339 | 340 | class MyTypeExtension extends AbstractTypeExtension 341 | { 342 | public function getExtendedType() 343 | { 344 | return CoumnType::class; 345 | } 346 | } 347 | ``` 348 | 349 | * Returning type instances from `ColumnTypeInterface::getParent()` is not supported anymore. 350 | Return the fully-qualified class name of the parent type class instead. 351 | 352 | Before: 353 | 354 | ```php 355 | class MyType extends AbstractColumnType 356 | { 357 | public function getParent() 358 | { 359 | return new ParentType(); 360 | } 361 | } 362 | ``` 363 | 364 | After: 365 | 366 | ```php 367 | class MyType extends AbstractType 368 | { 369 | public function getParent() 370 | { 371 | return ParentType::class; 372 | } 373 | } 374 | ``` 375 | 376 | * Passing type instances to `DatagridBuilder::add()` and the 377 | `DatagragridFactory::createCoumn()` methods is not supported anymore. 378 | Pass the fully-qualified class name of the type instead. 379 | 380 | Before: 381 | 382 | ```php 383 | $column = $datagridBuilder->createColumn('name', new MyType()); 384 | ``` 385 | 386 | After: 387 | 388 | ```php 389 | $column = $datagridBuilder->createColumn('name', MyType::class); 390 | ``` 391 | 392 | * The DataMapper is removed in favor of an easier and faster solution. 393 | Instead of setting a DataMapper you set a data-provider callable on the column. 394 | 395 | Before: 396 | 397 | ```php 398 | $registry = ...; 399 | $dataProvider = ...; 400 | $datagrid = ...; 401 | 402 | $datagridFactory = new DatagridFactory($registry, $dataProvider); 403 | $datagridFactory->createColumn('name', TextType::class, $datagrid, ['label' => 'Name', 'field_mapping' => ['name' => 'name']]); 404 | ``` 405 | 406 | After: 407 | 408 | ```php 409 | $registry = ...; 410 | 411 | $datagridFactory = new DatagridFactory($registry); 412 | $datagridFactory->createColumn( 413 | 'name', 414 | TextType::class, 415 | ['label' => 'Name', 'data_provider' => function ($data) { $data->getName(); }] 416 | ); 417 | ``` 418 | 419 | **Note:** If your column-type doesn't have the `ColumnType` as it's parent 420 | you need to call `setDataProvider()` in your custom column-type. 421 | 422 | **Tip:** 423 | 424 | > If you don't provide a value for 'data_provider', the `ColumnType` will try to create a data-provider. 425 | > This only works when the the property-path name equals to the column-name. 426 | > So when your column is named "name", eg. a public property "name", method named getName() 427 | > or magic `__get()` method must exist on the row's data-source for this work. 428 | > 429 | > ```php 430 | > $registry = ...; 431 | > 432 | > $datagridFactory = new DatagridFactory($registry); 433 | > $datagridFactory->createColumn('name', TextType::class, ['label' => 'Name']); 434 | > ``` 435 | 436 | * `Rollerworks\Component\Datagrid\Extension\Core\ColumnType\ModelType` is removed. 437 | Instead you can use the `Rollerworks\Component\Datagrid\Extension\Core\Type\TextTypeType` with multiple 438 | fields returned by the "data_provider" and the "value_format" option set to a callback. 439 | And no "glue" set, the "value_format" as callback will receive all the fields allowing to fully customize 440 | the returned format. 441 | 442 | Or create a custom type to handle the value returned by your data_provider. 443 | 444 | * The following transformer where removed as they are no longer needed: 445 | 446 | * `Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\TrimTransformer` 447 | * `Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\SingleMappingTransformer` 448 | * `Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\NestedListTransformer` 449 | * `Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\ModelToArrayTransformer` 450 | 451 | * The empty value handling of the `TextTransformer` has moved to its own Transformer 452 | `Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\EmptyValueTransformer`. 453 | 454 | * The 'label' option is now optional, you should "humanize" the column name when no label 455 | is provided. 456 | 457 | You can use the following snippet to humanize a column name: 458 | 459 | ```php 460 | $text = 'columnName'; 461 | $label = ucfirst(trim(strtolower(preg_replace(array('/([A-Z])/', '/[_\s]+/'), array('_$1', ' '), $text)))); 462 | ``` 463 | 464 | ### Views 465 | 466 | * All the view interfaces are removed, and only classes are provided. 467 | All view classes have public properties and can be extended when needed. 468 | 469 | * Rows are now initialized when the DatagridView is created, and not when the row 470 | gets iterated. 471 | 472 | * Views are no longer aware of the object that created them, meaning it's no longer possible 473 | to directly get the Datagrid or Column object from a view. 474 | 475 | Instead any information should be set on the view's `vars` property instead. 476 | 477 | * When a DatagridView is created it's no longer possible to change the column order 478 | or it's cells. Existing rows can be removed but not added or replaced. 479 | 480 | * Updating the `DatagridView` class is changed to use a callable instead of looping 481 | through event listeners. 482 | 483 | ## Upgrade FROM 0.5 to 0.6 484 | 485 | * The of methods signature of `buildColumn()`, `buildHeaderView()` and `buildCellView()` 486 | on the `Rollerworks\Component\Datagrid\Column\ColumnTypeExtensionInterface` was changed 487 | to be consistent with the `Rollerworks\Component\Datagrid\Column\ColumnTypeInterface`. 488 | 489 | Before: 490 | 491 | ```php 492 | /** 493 | * @param ColumnInterface $column 494 | */ 495 | public function buildColumn(ColumnInterface $column); 496 | 497 | /** 498 | * @param ColumnInterface $column 499 | * @param HeaderView $view 500 | */ 501 | public function buildHeaderView(ColumnInterface $column, HeaderView $view); 502 | 503 | /** 504 | * @param ColumnInterface $column 505 | * @param CellView $view 506 | */ 507 | public function buildCellView(ColumnInterface $column, CellView $view); 508 | ``` 509 | 510 | After: 511 | 512 | ```php 513 | /** 514 | * @param ColumnInterface $column 515 | * @param array $options 516 | */ 517 | public function buildColumn(ColumnInterface $column, array $options); 518 | 519 | /** 520 | * @param HeaderView $view 521 | * @param ColumnInterface $column 522 | * @param array $options 523 | */ 524 | public function buildHeaderView(HeaderView $view, ColumnInterface $column, array $options); 525 | 526 | /** 527 | * @param CellView $view 528 | * @param ColumnInterface $column 529 | * @param array $options 530 | */ 531 | public function buildCellView(CellView $view, ColumnInterface $column, array $options); 532 | ``` 533 | 534 | ## Upgrade FROM 0.4 to 0.5 535 | 536 | ### Field mapping configuration 537 | 538 | * The "field_mapping" option now only accepts an associative array, 539 | where the key is used to identify a mapping-field, the value holds 540 | the mapping-path. 541 | 542 | Before: `['field_mapping' => ['user.id']]` 543 | After: `['field_mapping' => ['user_id' => 'user.id', 'id' => 'id']]` 544 | 545 | * Column types with multiple fields will receive the data like: 546 | 547 | ```php 548 | // Keys are as configured (shown above) 549 | $values = [ 550 | 'id' => 50, 551 | 'user_id' => 10, 552 | ]; 553 | ``` 554 | 555 | ### ActionType 556 | 557 | The "action" type has been completely rewritten to be more extensible. 558 | 559 | * Option "content" was added as an alternative to the "label" option, 560 | you can use eg. the "label" option or "content". 561 | 562 | * Option "url" was added and allows to configure a complete uri (instead of a pattern). 563 | 564 | * Option "uri_scheme" now uses `strtr()` instead of the `sprintf()` pattern 565 | for formatting an uri. 566 | 567 | The replacement values are provided as `{id}` for the `id` mapping key 568 | (see above for details). 569 | 570 | * Instead of configuring multiple actions, you must now use the "compound_column" 571 | type to combine multiple actions in a cell. 572 | 573 | Before: 574 | 575 | ```php 576 | $datagrid->addColumn( 577 | $this->factory->createColumn( 578 | 'actions', 579 | 'action', 580 | $datagrid, 581 | [ 582 | 'label' => 'actions', 583 | 'field_mapping' => ['id'], 584 | 'actions' => [ 585 | 'modify' => [ 586 | 'label' => 'Modify', 587 | 'uri_scheme' => 'entity/%d/modify', 588 | ], 589 | 'delete' => [ 590 | 'label' => 'Delete', 591 | 'uri_scheme' => 'entity/%d/delete', 592 | ], 593 | ] 594 | ] 595 | ) 596 | ); 597 | ``` 598 | 599 | After: 600 | 601 | ```php 602 | $datagrid->addColumn( 603 | $this->factory->createColumn( 604 | 'actions', 605 | 'compound_column', 606 | $datagrid, 607 | [ 608 | 'label' => 'Actions', 609 | 'columns' => [ 610 | 'modify' => $this->factory->createColumn( 611 | 'modify', 612 | 'action', 613 | $datagrid, 614 | [ 615 | 'label' => 'Modify', 616 | 'field_mapping' => ['id' => 'id'], 617 | 'uri_scheme' => 'entity/{id}/modify', 618 | ] 619 | ), 620 | 'delete' => $this->factory->createColumn( 621 | 'delete', 622 | 'action', 623 | $datagrid, 624 | [ 625 | 'label' => 'Delete', 626 | 'field_mapping' => ['id' => 'id'], 627 | 'uri_scheme' => 'entity/{id}/delete', 628 | ] 629 | ), 630 | ] 631 | ] 632 | ) 633 | ); 634 | ``` 635 | 636 | ## Upgrade FROM 0.3 to 0.4 637 | 638 | * No changes required. 639 | 640 | ## Upgrade FROM 0.2 to 0.3 641 | 642 | * The methods `setVar()`, `getVar()` and `getVars()` were added 643 | to `Rollerworks\Component\Datagrid\DatagridViewInterface`. If you implemented 644 | this interface in your own code, you should add these three methods. 645 | 646 | ## Upgrade FROM 0.1 to 0.2 647 | 648 | * The methods `createDatagridBuilder()` as added 649 | to `Rollerworks\Component\Datagrid\DatagridFactoryInterface`. If you implemented 650 | this interface in your own code, you should add this method. 651 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rollerworks/datagrid", 3 | "description": "Datagrid system with a modular design and customizable", 4 | "keywords": ["rollerworks", "component", "datagrid", "table", "ui"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Sebastiaan Stok", 10 | "email": "s.stok@rollercapes.net" 11 | }, 12 | { 13 | "name": "Community contributions", 14 | "homepage": "https://github.com/rollerworks/datagrid/contributors" 15 | } 16 | ], 17 | "require": { 18 | "php": "^7.0", 19 | "symfony/property-access": "^3.0", 20 | "symfony/options-resolver": "^3.0", 21 | "symfony/intl": "^3.2.6" 22 | }, 23 | "require-dev": { 24 | "symfony/phpunit-bridge": "^3.2.6", 25 | "sllh/php-cs-fixer-styleci-bridge": "^2.1" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Rollerworks\\Component\\Datagrid\\": "src/" 30 | }, 31 | "exclude-from-classmap": ["Tests/", "test/"] 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Rollerworks\\Component\\Datagrid\\Tests\\": "tests/" 36 | } 37 | }, 38 | "extra": { 39 | "branch-alias": { 40 | "dev-master": "0.11-dev" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/AbstractDatagridExtension.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Column\ColumnTypeExtensionInterface; 17 | use Rollerworks\Component\Datagrid\Column\ColumnTypeInterface; 18 | use Rollerworks\Component\Datagrid\Exception\InvalidArgumentException; 19 | use Rollerworks\Component\Datagrid\Exception\UnexpectedTypeException; 20 | 21 | /** 22 | * @author Sebastiaan Stok 23 | */ 24 | abstract class AbstractDatagridExtension implements DatagridExtensionInterface 25 | { 26 | /** 27 | * All column types extensions provided by Datagrid extension. 28 | * 29 | * @var array 30 | */ 31 | private $typesExtensions; 32 | 33 | /** 34 | * All column types provided by extension. 35 | * 36 | * @var array 37 | */ 38 | private $types; 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function getType(string $name): ColumnTypeInterface 44 | { 45 | if (null === $this->types) { 46 | $this->initColumnTypes(); 47 | } 48 | 49 | if (!isset($this->types[$name])) { 50 | throw new InvalidArgumentException(sprintf('Column type "%s" can not be loaded by this extension.', $name)); 51 | } 52 | 53 | return $this->types[$name]; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function hasType(string $name): bool 60 | { 61 | if (null === $this->types) { 62 | $this->initColumnTypes(); 63 | } 64 | 65 | return isset($this->types[$name]); 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | public function hasTypeExtensions(string $type): bool 72 | { 73 | if (null === $this->typesExtensions) { 74 | $this->initTypesExtensions(); 75 | } 76 | 77 | return isset($this->typesExtensions[$type]); 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function getTypeExtensions(string $type): array 84 | { 85 | if (null === $this->typesExtensions) { 86 | $this->initTypesExtensions(); 87 | } 88 | 89 | return isset($this->typesExtensions[$type]) ? $this->typesExtensions[$type] : []; 90 | } 91 | 92 | /** 93 | * If extension needs to provide new column types this function 94 | * should be overloaded in child class and return an array of ColumnTypeInterface 95 | * instances. 96 | * 97 | * @return ColumnTypeInterface[] 98 | * 99 | * @codeCoverageIgnore 100 | */ 101 | protected function loadTypes(): array 102 | { 103 | return []; 104 | } 105 | 106 | /** 107 | * If extension needs to provide new column types this function 108 | * should be overloaded in child class and return array of ColumnTypeInterface 109 | * instances. 110 | * 111 | * @return array 112 | * 113 | * @codeCoverageIgnore 114 | */ 115 | protected function loadTypesExtensions(): array 116 | { 117 | return []; 118 | } 119 | 120 | /** 121 | * @throws UnexpectedTypeException 122 | */ 123 | private function initColumnTypes() 124 | { 125 | $this->types = []; 126 | 127 | foreach ($this->loadTypes() as $type) { 128 | if (!$type instanceof ColumnTypeInterface) { 129 | throw new UnexpectedTypeException($type, ColumnTypeInterface::class); 130 | } 131 | 132 | $this->types[get_class($type)] = $type; 133 | } 134 | } 135 | 136 | /** 137 | * @throws UnexpectedTypeException 138 | */ 139 | private function initTypesExtensions() 140 | { 141 | $this->typesExtensions = []; 142 | 143 | foreach ($this->loadTypesExtensions() as $extension) { 144 | if (!$extension instanceof ColumnTypeExtensionInterface) { 145 | throw new UnexpectedTypeException($extension, ColumnTypeExtensionInterface::class); 146 | } 147 | 148 | $type = $extension->getExtendedType(); 149 | 150 | $this->typesExtensions[$type][] = $extension; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/BaseView.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | /** 17 | * Base class for all view related classes. 18 | * 19 | * This class can be used as a type hint, but should 20 | * not be directly extended by external classes. 21 | */ 22 | abstract class BaseView 23 | { 24 | /** 25 | * Extra variables for view rendering. 26 | * 27 | * It's possible to set values directly. 28 | * But the property type itself should not be changed! 29 | * 30 | * @var array 31 | */ 32 | public $vars = []; 33 | 34 | /** 35 | * Get a variable value by key. 36 | * 37 | * This method should only be used when the key can null. 38 | * Else it's faster to get the var's value directly. 39 | * 40 | * @param string $key 41 | * @param mixed $default 42 | * 43 | * @return mixed 44 | */ 45 | public function getVar(string $key, $default = null) 46 | { 47 | if (array_key_exists($key, $this->vars)) { 48 | return $this->vars[$key]; 49 | } 50 | 51 | return $default; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Column/AbstractType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Rollerworks\Component\Datagrid\Extension\Core\Type\ColumnType; 17 | use Rollerworks\Component\Datagrid\Util\StringUtil; 18 | use Symfony\Component\OptionsResolver\OptionsResolver; 19 | 20 | /** 21 | * @author Sebastiaan Stok 22 | */ 23 | abstract class AbstractType implements ColumnTypeInterface 24 | { 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function configureOptions(OptionsResolver $resolver) 29 | { 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function buildColumn(ColumnInterface $column, array $options) 36 | { 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function buildCellView(CellView $view, ColumnInterface $column, array $options) 43 | { 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function buildHeaderView(HeaderView $view, ColumnInterface $column, array $options) 50 | { 51 | } 52 | 53 | /** 54 | * Returns the prefix of the template block name for this type. 55 | * 56 | * The block prefixes default to the underscored short class name with 57 | * the "Type" suffix removed (e.g. "DatetimeType" => "datetime"). 58 | * 59 | * @return string The prefix of the template block name 60 | */ 61 | public function getBlockPrefix(): string 62 | { 63 | return StringUtil::fqcnToBlockPrefix(get_class($this)); 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function getParent() 70 | { 71 | return ColumnType::class; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Column/AbstractTypeExtension.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Symfony\Component\OptionsResolver\OptionsResolver; 17 | 18 | /** 19 | * @author Sebastiaan Stok 20 | */ 21 | abstract class AbstractTypeExtension implements ColumnTypeExtensionInterface 22 | { 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function buildColumn(ColumnInterface $column, array $options) 27 | { 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function buildHeaderView(HeaderView $view, ColumnInterface $column, array $options) 34 | { 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function buildCellView(CellView $view, ColumnInterface $column, array $options) 41 | { 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function configureOptions(OptionsResolver $optionsResolver) 48 | { 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Column/CellView.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Rollerworks\Component\Datagrid\BaseView; 17 | use Rollerworks\Component\Datagrid\DatagridView; 18 | 19 | /** 20 | * CellView provides the data for rendering the Datagrid cell. 21 | * 22 | * @author Sebastiaan Stok 23 | */ 24 | class CellView extends BaseView 25 | { 26 | /** 27 | * Cell value. 28 | * 29 | * In most cases this will be a simple string. 30 | * 31 | * @var mixed 32 | */ 33 | public $value; 34 | 35 | /** 36 | * Use the content as raw (as-is) without escaping. 37 | * 38 | * WARNING! this requires a save value to prevent XSS. 39 | * 40 | * @var bool 41 | */ 42 | public $useRaw = false; 43 | 44 | /** 45 | * @var array|object 46 | */ 47 | public $source; 48 | 49 | /** 50 | * @var HeaderView 51 | */ 52 | public $column; 53 | 54 | /** 55 | * @var DatagridView 56 | */ 57 | public $datagrid; 58 | 59 | /** 60 | * @var string 61 | */ 62 | public $name; 63 | 64 | /** 65 | * Constructor. 66 | * 67 | * @param HeaderView $column 68 | * @param DatagridView $datagrid 69 | */ 70 | public function __construct(HeaderView $column, DatagridView $datagrid) 71 | { 72 | $this->name = $column->name; 73 | $this->datagrid = $datagrid; 74 | $this->column = $column; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Column/Column.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Rollerworks\Component\Datagrid\DatagridView; 17 | use Rollerworks\Component\Datagrid\DataTransformerInterface; 18 | use Rollerworks\Component\Datagrid\Exception\BadMethodCallException; 19 | 20 | /** 21 | * @author Sebastiaan Stok 22 | */ 23 | class Column implements ColumnInterface 24 | { 25 | /** 26 | * @var string 27 | */ 28 | private $name; 29 | 30 | /** 31 | * @var ResolvedColumnTypeInterface 32 | */ 33 | private $type; 34 | 35 | /** 36 | * @var array 37 | */ 38 | protected $options; 39 | 40 | /** 41 | * @var bool 42 | */ 43 | protected $locked = false; 44 | 45 | /** 46 | * @var DataTransformerInterface|null 47 | */ 48 | private $viewTransformer; 49 | 50 | /** 51 | * @var callable 52 | */ 53 | private $dataProvider; 54 | 55 | /** 56 | * Constructor. 57 | * 58 | * @param string $name 59 | * @param ResolvedColumnTypeInterface $type 60 | * @param array $options 61 | * 62 | * @throws \InvalidArgumentException when the name is invalid 63 | */ 64 | public function __construct( 65 | $name, 66 | ResolvedColumnTypeInterface $type, 67 | array $options = [] 68 | ) { 69 | if ('' === $name) { 70 | throw new \InvalidArgumentException( 71 | sprintf( 72 | 'The name "%s" contains illegal characters. Names should start with a letter, digit or underscore '. 73 | 'and only contain letters, digits, numbers, underscores ("_"), hyphens ("-") and colons (":").', 74 | $name 75 | ) 76 | ); 77 | } 78 | 79 | $this->name = $name; 80 | $this->type = $type; 81 | $this->options = $options; 82 | $this->locked = false; 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function getName(): string 89 | { 90 | return $this->name; 91 | } 92 | 93 | /** 94 | * {@inheritdoc} 95 | */ 96 | public function getType(): ResolvedColumnTypeInterface 97 | { 98 | return $this->type; 99 | } 100 | 101 | /** 102 | * {@inheritdoc} 103 | */ 104 | public function setViewTransformer(DataTransformerInterface $viewTransformer = null) 105 | { 106 | if ($this->locked) { 107 | throw new BadMethodCallException('Column setter methods cannot be accessed anymore once the data is locked.'); 108 | } 109 | 110 | $this->viewTransformer = $viewTransformer; 111 | } 112 | 113 | /** 114 | * {@inheritdoc} 115 | */ 116 | public function getViewTransformer() 117 | { 118 | return $this->viewTransformer; 119 | } 120 | 121 | /** 122 | * {@inheritdoc} 123 | */ 124 | public function getOptions(): array 125 | { 126 | return $this->options; 127 | } 128 | 129 | /** 130 | * {@inheritdoc} 131 | */ 132 | public function getOption(string $name, $default = null) 133 | { 134 | if (array_key_exists($name, $this->options)) { 135 | return $this->options[$name]; 136 | } 137 | 138 | return $default; 139 | } 140 | 141 | /** 142 | * {@inheritdoc} 143 | */ 144 | public function hasOption(string $name): bool 145 | { 146 | return array_key_exists($name, $this->options); 147 | } 148 | 149 | /** 150 | * {@inheritdoc} 151 | */ 152 | public function createHeaderView(DatagridView $datagrid): HeaderView 153 | { 154 | // The methods createHeaderView(), buildHeaderView() are called 155 | // explicitly here in order to be able to override either of them 156 | // in a custom resolved column type. 157 | 158 | $view = $this->type->createHeaderView($this, $datagrid); 159 | $this->type->buildHeaderView($view, $this, $this->options); 160 | 161 | return $view; 162 | } 163 | 164 | /** 165 | * {@inheritdoc} 166 | */ 167 | public function createCellView(HeaderView $header, $object, $index): CellView 168 | { 169 | // The methods createCellView(), buildCellView() are called 170 | // explicitly here in order to be able to override either of them 171 | // in a custom resolved column type. 172 | 173 | $view = $this->type->createCellView($this, $header); 174 | 175 | $view->vars['row'] = $index; 176 | $view->value = $this->type->getValue($this, $object); 177 | $view->source = $object; 178 | 179 | $this->type->buildCellView($view, $this, $this->options); 180 | 181 | return $view; 182 | } 183 | 184 | /** 185 | * Set the data-provider for the column. 186 | * 187 | * @param callable $dataProvider 188 | */ 189 | public function setDataProvider(callable $dataProvider) 190 | { 191 | $this->dataProvider = $dataProvider; 192 | } 193 | 194 | /** 195 | * Get data-provider for this column. 196 | * 197 | * @return callable 198 | */ 199 | public function getDataProvider(): callable 200 | { 201 | return $this->dataProvider; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/Column/ColumnInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Rollerworks\Component\Datagrid\DatagridView; 17 | use Rollerworks\Component\Datagrid\DataTransformerInterface; 18 | 19 | /** 20 | * @author Sebastiaan Stok 21 | */ 22 | interface ColumnInterface 23 | { 24 | /** 25 | * Returns the name of the column in the Datagrid. 26 | * 27 | * @return string 28 | */ 29 | public function getName(): string; 30 | 31 | /** 32 | * Returns the column-type name. 33 | * 34 | * @return ResolvedColumnTypeInterface 35 | */ 36 | public function getType(): ResolvedColumnTypeInterface; 37 | 38 | /** 39 | * Set the view transform of the column. 40 | * 41 | * The transform method of the transformer is used to convert data from the 42 | * normalized to the view format. 43 | * 44 | * @param DataTransformerInterface|null $viewTransformer 45 | */ 46 | public function setViewTransformer(DataTransformerInterface $viewTransformer = null); 47 | 48 | /** 49 | * Returns the view transformer of the column. 50 | * 51 | * @return DataTransformerInterface|null 52 | */ 53 | public function getViewTransformer(); 54 | 55 | /** 56 | * Returns all options passed during the construction of the column. 57 | * 58 | * @return array The passed options 59 | */ 60 | public function getOptions(): array; 61 | 62 | /** 63 | * Returns the value of a specific option. 64 | * 65 | * @param string $name The option name 66 | * @param mixed $default The value returned if the option does not exist 67 | * 68 | * @return mixed The option value 69 | */ 70 | public function getOption(string $name, $default = null); 71 | 72 | /** 73 | * Returns whether a specific option exists. 74 | * 75 | * @param string $name 76 | * 77 | * @return bool 78 | */ 79 | public function hasOption(string $name): bool; 80 | 81 | /** 82 | * @param DatagridView $datagrid 83 | * 84 | * @return HeaderView 85 | */ 86 | public function createHeaderView(DatagridView $datagrid): HeaderView; 87 | 88 | /** 89 | * @param HeaderView $header 90 | * @param object $object 91 | * @param int|string $index 92 | * 93 | * @return CellView 94 | */ 95 | public function createCellView(HeaderView $header, $object, $index): CellView; 96 | 97 | /** 98 | * Set the data-provider for the column. 99 | * 100 | * @param callable $dataProvider 101 | */ 102 | public function setDataProvider(callable $dataProvider); 103 | 104 | /** 105 | * Get data-provider for this column. 106 | * 107 | * @return callable 108 | */ 109 | public function getDataProvider(): callable; 110 | } 111 | -------------------------------------------------------------------------------- /src/Column/ColumnTypeExtensionInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Symfony\Component\OptionsResolver\OptionsResolver; 17 | 18 | /** 19 | * @author Sebastiaan Stok 20 | */ 21 | interface ColumnTypeExtensionInterface 22 | { 23 | /** 24 | * @param ColumnInterface $column 25 | * @param array $options 26 | */ 27 | public function buildColumn(ColumnInterface $column, array $options); 28 | 29 | /** 30 | * @param HeaderView $view 31 | * @param ColumnInterface $column 32 | * @param array $options 33 | */ 34 | public function buildHeaderView(HeaderView $view, ColumnInterface $column, array $options); 35 | 36 | /** 37 | * @param CellView $view 38 | * @param ColumnInterface $column 39 | * @param array $options 40 | */ 41 | public function buildCellView(CellView $view, ColumnInterface $column, array $options); 42 | 43 | /** 44 | * Configures the default options for this type. 45 | * 46 | * @param OptionsResolver $optionsResolver 47 | */ 48 | public function configureOptions(OptionsResolver $optionsResolver); 49 | 50 | /** 51 | * Returns the name of the type being extended. 52 | * 53 | * @return string The name of the type being extended 54 | */ 55 | public function getExtendedType(): string; 56 | } 57 | -------------------------------------------------------------------------------- /src/Column/ColumnTypeInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Symfony\Component\OptionsResolver\OptionsResolver; 17 | 18 | /** 19 | * @author Sebastiaan Stok 20 | */ 21 | interface ColumnTypeInterface 22 | { 23 | /** 24 | * Sets the default options for this type. 25 | * 26 | * @param OptionsResolver $resolver The resolver for the options 27 | */ 28 | public function configureOptions(OptionsResolver $resolver); 29 | 30 | /** 31 | * Configures the column. 32 | * 33 | * @param ColumnInterface $column The column 34 | * @param array $options The resolved options 35 | */ 36 | public function buildColumn(ColumnInterface $column, array $options); 37 | 38 | /** 39 | * Configures the CellView instance. 40 | * 41 | * @param CellView $view The view 42 | * @param ColumnInterface $column The column 43 | * @param array $options The resolved options 44 | */ 45 | public function buildCellView(CellView $view, ColumnInterface $column, array $options); 46 | 47 | /** 48 | * Configures the HeaderView instance. 49 | * 50 | * @param HeaderView $view The view 51 | * @param ColumnInterface $column The column 52 | * @param array $options The resolved options 53 | */ 54 | public function buildHeaderView(HeaderView $view, ColumnInterface $column, array $options); 55 | 56 | /** 57 | * Returns the prefix of the template block name for this type. 58 | * 59 | * @return string The prefix of the template block name 60 | */ 61 | public function getBlockPrefix(): string; 62 | 63 | /** 64 | * Returns the fully-qualified class name of the parent type. 65 | * 66 | * @return string|null The name of the parent type if any, null otherwise 67 | */ 68 | public function getParent(); 69 | } 70 | -------------------------------------------------------------------------------- /src/Column/ColumnTypeRegistry.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Rollerworks\Component\Datagrid\DatagridExtensionInterface; 17 | use Rollerworks\Component\Datagrid\Exception\ExceptionInterface; 18 | use Rollerworks\Component\Datagrid\Exception\InvalidArgumentException; 19 | use Rollerworks\Component\Datagrid\Exception\UnexpectedTypeException; 20 | 21 | /** 22 | * @author Sebastiaan Stok 23 | */ 24 | class ColumnTypeRegistry implements ColumnTypeRegistryInterface 25 | { 26 | /** 27 | * Extensions. 28 | * 29 | * @var DatagridExtensionInterface[] 30 | */ 31 | private $extensions = []; 32 | 33 | /** 34 | * @var array 35 | */ 36 | private $types = []; 37 | 38 | /** 39 | * @var ResolvedColumnTypeFactoryInterface 40 | */ 41 | private $resolvedTypeFactory; 42 | 43 | /** 44 | * Constructor. 45 | * 46 | * @param DatagridExtensionInterface[] $extensions An array of DatagridExtensionInterface 47 | * @param ResolvedColumnTypeFactoryInterface $resolvedTypeFactory The factory for resolved column types 48 | * 49 | * @throws UnexpectedTypeException if an extension does not implement DatagridExtensionInterface 50 | */ 51 | public function __construct(array $extensions, ResolvedColumnTypeFactoryInterface $resolvedTypeFactory) 52 | { 53 | foreach ($extensions as $extension) { 54 | if (!$extension instanceof DatagridExtensionInterface) { 55 | throw new UnexpectedTypeException($extension, DatagridExtensionInterface::class); 56 | } 57 | } 58 | 59 | $this->extensions = $extensions; 60 | $this->resolvedTypeFactory = $resolvedTypeFactory; 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function getType(string $name): ResolvedColumnTypeInterface 67 | { 68 | if (!isset($this->types[$name])) { 69 | $type = null; 70 | 71 | foreach ($this->extensions as $extension) { 72 | if ($extension->hasType($name)) { 73 | $type = $extension->getType($name); 74 | 75 | break; 76 | } 77 | } 78 | 79 | if (!$type) { 80 | // Support fully-qualified class names. 81 | if (!class_exists($name) || !in_array(ColumnTypeInterface::class, class_implements($name), true)) { 82 | throw new InvalidArgumentException(sprintf('Could not load type "%s"', $name)); 83 | } 84 | 85 | $type = new $name(); 86 | } 87 | 88 | $this->types[$name] = $this->resolveType($type); 89 | } 90 | 91 | return $this->types[$name]; 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function hasType(string $name): bool 98 | { 99 | if (isset($this->types[$name])) { 100 | return true; 101 | } 102 | 103 | try { 104 | $this->getType($name); 105 | } catch (ExceptionInterface $e) { 106 | return false; 107 | } 108 | 109 | return true; 110 | } 111 | 112 | /** 113 | * {@inheritdoc} 114 | */ 115 | public function getExtensions(): array 116 | { 117 | return $this->extensions; 118 | } 119 | 120 | private function resolveType(ColumnTypeInterface $type): ResolvedColumnTypeInterface 121 | { 122 | $parentType = $type->getParent(); 123 | $fqcn = get_class($type); 124 | 125 | $typeExtensions = []; 126 | 127 | foreach ($this->extensions as $extension) { 128 | $typeExtensions = array_merge( 129 | $typeExtensions, 130 | $extension->getTypeExtensions($fqcn) 131 | ); 132 | } 133 | 134 | return $this->resolvedTypeFactory->createResolvedType( 135 | $type, 136 | $typeExtensions, 137 | $parentType ? $this->getType($parentType) : null 138 | ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Column/ColumnTypeRegistryInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Rollerworks\Component\Datagrid\DatagridExtensionInterface; 17 | use Rollerworks\Component\Datagrid\Exception\InvalidArgumentException; 18 | 19 | /** 20 | * @author Sebastiaan Stok 21 | */ 22 | interface ColumnTypeRegistryInterface 23 | { 24 | /** 25 | * Returns a column type by name. 26 | * 27 | * This methods registers the type extensions from the datagrid extensions. 28 | * 29 | * @param string $name The name of the type 30 | * 31 | * @throws InvalidArgumentException if the type can not be retrieved from any extension 32 | * 33 | * @return ResolvedColumnTypeInterface The type 34 | */ 35 | public function getType(string $name): ResolvedColumnTypeInterface; 36 | 37 | /** 38 | * Returns whether the given column type is supported. 39 | * 40 | * @param string $name The name of the type 41 | * 42 | * @return bool Whether the type is supported 43 | */ 44 | public function hasType(string $name): bool; 45 | 46 | /** 47 | * Returns the extensions loaded by the component. 48 | * 49 | * @return DatagridExtensionInterface[] 50 | */ 51 | public function getExtensions(): array; 52 | } 53 | -------------------------------------------------------------------------------- /src/Column/CompoundColumn.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Rollerworks\Component\Datagrid\DatagridView; 17 | use Rollerworks\Component\Datagrid\Exception\BadMethodCallException; 18 | 19 | class CompoundColumn extends Column 20 | { 21 | private $columns = []; 22 | 23 | public function createHeaderView(DatagridView $datagrid): HeaderView 24 | { 25 | if (!$this->locked || !$this->columns) { 26 | throw new BadMethodCallException( 27 | 'Cannot be create a headerView, the Column is not properly configured.' 28 | ); 29 | } 30 | 31 | return parent::createHeaderView($datagrid); 32 | } 33 | 34 | public function createCellView(HeaderView $header, $object, $index): CellView 35 | { 36 | if (!$this->locked || !$this->columns) { 37 | throw new BadMethodCallException( 38 | 'Cannot be create a cellView, the Column is not properly configured.' 39 | ); 40 | } 41 | 42 | return parent::createCellView($header, $object, $index); 43 | } 44 | 45 | /** 46 | * @param ColumnInterface[] $columns 47 | */ 48 | public function setColumns(array $columns) 49 | { 50 | $this->columns = $columns; 51 | $this->locked = true; 52 | } 53 | 54 | /** 55 | * @return ColumnInterface[] 56 | */ 57 | public function getColumns(): array 58 | { 59 | return $this->columns; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Column/HeaderView.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Rollerworks\Component\Datagrid\BaseView; 17 | use Rollerworks\Component\Datagrid\DatagridView; 18 | 19 | /** 20 | * @author Sebastiaan Stok 21 | */ 22 | class HeaderView extends BaseView 23 | { 24 | /** 25 | * @var string 26 | */ 27 | public $label; 28 | 29 | /** 30 | * @var DatagridView 31 | */ 32 | public $datagrid; 33 | 34 | /** 35 | * @var string 36 | */ 37 | public $name; 38 | 39 | /** 40 | * @param ColumnInterface $column 41 | * @param DatagridView $datagrid 42 | * @param string $label 43 | */ 44 | public function __construct(ColumnInterface $column, DatagridView $datagrid, string $label = null) 45 | { 46 | $this->datagrid = $datagrid; 47 | $this->name = $column->getName(); 48 | $this->label = $label; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Column/ResolvedColumnType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Rollerworks\Component\Datagrid\DatagridView; 17 | use Rollerworks\Component\Datagrid\Exception\UnexpectedTypeException; 18 | use Rollerworks\Component\Datagrid\Extension\Core\Type\CompoundColumnType; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | 21 | /** 22 | * @author Sebastiaan Stok 23 | */ 24 | class ResolvedColumnType implements ResolvedColumnTypeInterface 25 | { 26 | /** 27 | * @var ColumnTypeInterface 28 | */ 29 | protected $innerType; 30 | 31 | /** 32 | * @var ResolvedColumnType 33 | */ 34 | protected $parent; 35 | 36 | /** 37 | * @var bool 38 | */ 39 | protected $compound = null; 40 | 41 | /** 42 | * @var ColumnTypeExtensionInterface[] 43 | */ 44 | private $typeExtensions; 45 | 46 | /** 47 | * @var OptionsResolver 48 | */ 49 | private $optionsResolver; 50 | 51 | /** 52 | * Constructor. 53 | * 54 | * @param ColumnTypeInterface $innerType 55 | * @param ColumnTypeExtensionInterface[] $typeExtensions 56 | * @param ResolvedColumnTypeInterface $parent 57 | * 58 | * @throws UnexpectedTypeException When one of the given extensions is not an ColumnTypeExtensionInterface 59 | */ 60 | public function __construct(ColumnTypeInterface $innerType, array $typeExtensions = [], ResolvedColumnTypeInterface $parent = null) 61 | { 62 | foreach ($typeExtensions as $extension) { 63 | if (!$extension instanceof ColumnTypeExtensionInterface) { 64 | throw new UnexpectedTypeException($extension, 'Rollerworks\Component\Datagrid\Column\ColumnTypeExtensionInterface'); 65 | } 66 | } 67 | 68 | $this->innerType = $innerType; 69 | $this->typeExtensions = $typeExtensions; 70 | $this->parent = $parent; 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function getBlockPrefix(): string 77 | { 78 | return $this->innerType->getBlockPrefix(); 79 | } 80 | 81 | /** 82 | * Returns the parent type. 83 | * 84 | * @return ResolvedColumnTypeInterface|null The parent type or null 85 | */ 86 | public function getParent() 87 | { 88 | return $this->parent; 89 | } 90 | 91 | /** 92 | * Returns the wrapped column type. 93 | * 94 | * @return ColumnTypeInterface The wrapped column type 95 | */ 96 | public function getInnerType(): ColumnTypeInterface 97 | { 98 | return $this->innerType; 99 | } 100 | 101 | /** 102 | * Returns the extensions of the wrapped column type. 103 | * 104 | * @return ColumnTypeExtensionInterface[] An array of {@link ColumnTypeExtensionInterface} instances 105 | */ 106 | public function getTypeExtensions(): array 107 | { 108 | return $this->typeExtensions; 109 | } 110 | 111 | /** 112 | * This configures the {@link ColumnInterface}. 113 | * 114 | * This method is called for each type in the hierarchy starting from the 115 | * top most type. Type extensions can further modify the column. 116 | * 117 | * @param ColumnInterface $config 118 | * @param array $options 119 | */ 120 | public function buildType(ColumnInterface $config, array $options) 121 | { 122 | if (null !== $this->parent) { 123 | $this->parent->buildType($config, $options); 124 | } 125 | 126 | $this->innerType->buildColumn($config, $options); 127 | 128 | foreach ($this->typeExtensions as $extension) { 129 | $extension->buildColumn($config, $options); 130 | } 131 | } 132 | 133 | /** 134 | * Returns a new ColumnInterface instance. 135 | * 136 | * @param string $name 137 | * @param array $options 138 | * 139 | * @return ColumnInterface 140 | */ 141 | public function createColumn(string $name, array $options = []): ColumnInterface 142 | { 143 | $options = $this->getOptionsResolver()->resolve($options); 144 | 145 | return $this->newColumn($name, $options); 146 | } 147 | 148 | /** 149 | * {@inheritdoc}. 150 | */ 151 | public function createHeaderView(ColumnInterface $column, DatagridView $datagrid): HeaderView 152 | { 153 | return new HeaderView($column, $datagrid, $column->getOption('label')); 154 | } 155 | 156 | /** 157 | * {@inheritdoc}. 158 | * 159 | * @param ColumnInterface $column 160 | * @param HeaderView $header 161 | * 162 | * @return CellView 163 | */ 164 | public function createCellView(ColumnInterface $column, HeaderView $header): CellView 165 | { 166 | return new CellView($header, $header->datagrid); 167 | } 168 | 169 | /** 170 | * Configures a header view for the type hierarchy. 171 | * 172 | * @param HeaderView $view 173 | * @param ColumnInterface $column 174 | * @param array $options 175 | */ 176 | public function buildHeaderView(HeaderView $view, ColumnInterface $column, array $options) 177 | { 178 | if (null !== $this->parent) { 179 | $this->parent->buildHeaderView($view, $column, $options); 180 | } 181 | 182 | $this->innerType->buildHeaderView($view, $column, $options); 183 | 184 | foreach ($this->typeExtensions as $extension) { 185 | $extension->buildHeaderView($view, $column, $options); 186 | } 187 | } 188 | 189 | /** 190 | * Configures a cell view for the type hierarchy. 191 | * 192 | * @param CellView $view 193 | * @param ColumnInterface $column 194 | * @param array $options 195 | */ 196 | public function buildCellView(CellView $view, ColumnInterface $column, array $options) 197 | { 198 | if (null !== $this->parent) { 199 | $this->parent->buildCellView($view, $column, $options); 200 | } 201 | 202 | $this->innerType->buildCellView($view, $column, $options); 203 | 204 | foreach ($this->typeExtensions as $extension) { 205 | $extension->buildCellView($view, $column, $options); 206 | } 207 | } 208 | 209 | /** 210 | * {@inheritdoc} 211 | */ 212 | public function getValue(ColumnInterface $column, $object) 213 | { 214 | $dataProvider = $column->getDataProvider(); 215 | $value = $dataProvider($object); 216 | 217 | if (null !== ($transformer = $column->getViewTransformer())) { 218 | $value = $transformer->transform($value); 219 | } 220 | 221 | return $value; 222 | } 223 | 224 | /** 225 | * Returns the configured options resolver used for this type. 226 | * 227 | * @return OptionsResolver The options resolver 228 | */ 229 | public function getOptionsResolver(): OptionsResolver 230 | { 231 | if (null === $this->optionsResolver) { 232 | if (null !== $this->parent) { 233 | $this->optionsResolver = clone $this->parent->getOptionsResolver(); 234 | } else { 235 | $this->optionsResolver = new OptionsResolver(); 236 | } 237 | 238 | $this->innerType->configureOptions($this->optionsResolver); 239 | 240 | foreach ($this->typeExtensions as $extension) { 241 | $extension->configureOptions($this->optionsResolver); 242 | } 243 | } 244 | 245 | return $this->optionsResolver; 246 | } 247 | 248 | /** 249 | * Creates a new Column instance. 250 | * 251 | * Override this method if you want to customize the Column class. 252 | * 253 | * @param string $name The name of the column 254 | * @param array $options The builder options 255 | * 256 | * @return ColumnInterface The new column instance 257 | */ 258 | protected function newColumn($name, array $options): ColumnInterface 259 | { 260 | // Special case of CompoundColumnType which requires that child columns 261 | // are set afterwards. Whenever you extend this class, make sure to honor this 262 | // special use-case. 263 | if (null === $this->compound) { 264 | $this->compound = $this->isCompound(); 265 | } 266 | 267 | if ($this->compound) { 268 | return new CompoundColumn($name, $this, $options); 269 | } 270 | 271 | return new Column($name, $this, $options); 272 | } 273 | 274 | /** 275 | * Determines whether this type is a compound. 276 | * 277 | * @return bool 278 | */ 279 | protected function isCompound(): bool 280 | { 281 | if ($this->innerType instanceof CompoundColumnType) { 282 | return true; 283 | } 284 | 285 | for ($type = $this->parent; null !== $type; $type = $type->getParent()) { 286 | if ($type->getInnerType() instanceof CompoundColumnType) { 287 | return true; 288 | } 289 | } 290 | 291 | return false; 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/Column/ResolvedColumnTypeFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | class ResolvedColumnTypeFactory implements ResolvedColumnTypeFactoryInterface 17 | { 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function createResolvedType(ColumnTypeInterface $type, array $typeExtensions, ResolvedColumnTypeInterface $parent = null): ResolvedColumnTypeInterface 22 | { 23 | return new ResolvedColumnType($type, $typeExtensions, $parent); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Column/ResolvedColumnTypeFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Rollerworks\Component\Datagrid\Exception\InvalidArgumentException; 17 | 18 | /** 19 | * @author Sebastiaan Stok 20 | */ 21 | interface ResolvedColumnTypeFactoryInterface 22 | { 23 | /** 24 | * Resolves a column type. 25 | * 26 | * @param ColumnTypeInterface $type 27 | * @param array $typeExtensions 28 | * @param ResolvedColumnTypeInterface $parent 29 | * 30 | * @throws InvalidArgumentException if the types parent can not be retrieved from any extension 31 | * 32 | * @return ResolvedColumnTypeInterface 33 | */ 34 | public function createResolvedType(ColumnTypeInterface $type, array $typeExtensions, ResolvedColumnTypeInterface $parent = null): ResolvedColumnTypeInterface; 35 | } 36 | -------------------------------------------------------------------------------- /src/Column/ResolvedColumnTypeInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Column; 15 | 16 | use Rollerworks\Component\Datagrid\DatagridView; 17 | use Symfony\Component\OptionsResolver\OptionsResolver; 18 | 19 | /** 20 | * A wrapper for a column type and its extensions. 21 | * 22 | * @author Sebastiaan Stok 23 | */ 24 | interface ResolvedColumnTypeInterface 25 | { 26 | /** 27 | * Returns the prefix of the template block name for this type. 28 | * 29 | * @return string The prefix of the template block name 30 | */ 31 | public function getBlockPrefix(): string; 32 | 33 | /** 34 | * Returns the parent type. 35 | * 36 | * @return ResolvedColumnTypeInterface|null The parent type or null 37 | */ 38 | public function getParent(); 39 | 40 | /** 41 | * Returns the wrapped column type. 42 | * 43 | * @return ColumnTypeInterface The wrapped column type 44 | */ 45 | public function getInnerType(): ColumnTypeInterface; 46 | 47 | /** 48 | * Returns the extensions of the wrapped column type. 49 | * 50 | * @return ColumnTypeExtensionInterface[] An array of {@link ColumnTypeExtensionInterface} instances 51 | */ 52 | public function getTypeExtensions(): array; 53 | 54 | /** 55 | * Returns a new ColumnInterface instance. 56 | * 57 | * @param string $name 58 | * @param array $options 59 | * 60 | * @return ColumnInterface 61 | */ 62 | public function createColumn(string $name, array $options = []): ColumnInterface; 63 | 64 | /** 65 | * This configures the {@link ColumnInterface} instance. 66 | * 67 | * This method is called for each type in the hierarchy starting from the 68 | * top most type. Type extensions can further modify the column. 69 | * 70 | * @param ColumnInterface $config 71 | * @param array $options 72 | */ 73 | public function buildType(ColumnInterface $config, array $options); 74 | 75 | /** 76 | * Creates a new header view for a column of this type. 77 | * 78 | * @param ColumnInterface $column 79 | * @param DatagridView $datagrid 80 | * 81 | * @return HeaderView 82 | */ 83 | public function createHeaderView(ColumnInterface $column, DatagridView $datagrid): HeaderView; 84 | 85 | /** 86 | * Creates a new cell view for a column of this type. 87 | * 88 | * @param ColumnInterface $column 89 | * @param HeaderView $header 90 | * 91 | * @return CellView 92 | */ 93 | public function createCellView(ColumnInterface $column, HeaderView $header): CellView; 94 | 95 | /** 96 | * Configures a header view for the type hierarchy. 97 | * 98 | * @param HeaderView $view 99 | * @param ColumnInterface $column 100 | * @param array $options 101 | */ 102 | public function buildHeaderView(HeaderView $view, ColumnInterface $column, array $options); 103 | 104 | /** 105 | * Configures a cell view for the type hierarchy. 106 | * 107 | * @param CellView $view 108 | * @param ColumnInterface $column 109 | * @param array $options 110 | */ 111 | public function buildCellView(CellView $view, ColumnInterface $column, array $options); 112 | 113 | /** 114 | * Returns the configured options resolver used for this type. 115 | * 116 | * @return OptionsResolver The options resolver 117 | */ 118 | public function getOptionsResolver(): OptionsResolver; 119 | 120 | /** 121 | * Get the value of the column. 122 | * 123 | * @param ColumnInterface $column 124 | * @param mixed $object 125 | * 126 | * @return mixed 127 | */ 128 | public function getValue(ColumnInterface $column, $object); 129 | } 130 | -------------------------------------------------------------------------------- /src/DataTransformerInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Exception\TransformationFailedException; 17 | 18 | /** 19 | * Transforms a value from different representations. 20 | * 21 | * @author Sebastiaan Stok 22 | */ 23 | interface DataTransformerInterface 24 | { 25 | /** 26 | * Transforms a value from the original representation to a transformed representation. 27 | * 28 | * This method is called to transform a normalized value to a 29 | * view representation eg. a localized or formatted representation. 30 | * 31 | * This method must be able to deal with empty values. Usually this will 32 | * be NULL, but depending on your implementation other empty values are 33 | * possible as well (such as empty strings). The reasoning behind this is 34 | * that value transformers must be chainable. If the transform() method 35 | * of the first value transformer outputs NULL, the second value transformer 36 | * must be able to process that value. 37 | * 38 | * By convention, transform() should return an empty string if NULL is 39 | * passed. 40 | * 41 | * @param mixed $value The value in the original representation 42 | * 43 | * @throws TransformationFailedException When the transformation fails 44 | * 45 | * @return mixed The value in the transformed representation 46 | */ 47 | public function transform($value); 48 | } 49 | -------------------------------------------------------------------------------- /src/Datagrid.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 17 | use Rollerworks\Component\Datagrid\Exception\BadMethodCallException; 18 | use Rollerworks\Component\Datagrid\Exception\InvalidArgumentException; 19 | use Rollerworks\Component\Datagrid\Exception\UnexpectedTypeException; 20 | use Rollerworks\Component\Datagrid\Exception\UnknownColumnException; 21 | 22 | /** 23 | * Default Datagrid implementation. 24 | * 25 | * This class should not be construct directly. 26 | * Use DatagridFactory::createDatagrid() DatagridFactory::createDatagridBuilder() 27 | * to create a new Datagrid. 28 | * 29 | * @author Sebastiaan Stok 30 | */ 31 | class Datagrid implements DatagridInterface 32 | { 33 | /** 34 | * Unique datagrid name. 35 | * 36 | * @var string 37 | */ 38 | private $name; 39 | 40 | /** 41 | * @var array|\Traversable|null 42 | */ 43 | private $data; 44 | 45 | /** 46 | * Datagrid columns. 47 | * 48 | * @var ColumnInterface[] 49 | */ 50 | private $columns = []; 51 | 52 | /** 53 | * @var callable 54 | */ 55 | private $viewBuilder; 56 | 57 | /** 58 | * Constructor. 59 | * 60 | * @param string $name Name of the datagrid 61 | * @param ColumnInterface[] $columns Columns of the datagrid 62 | * @param callable|null $viewBuilder A callable view builder. 63 | * Use the decorator pattern to chain multiple 64 | */ 65 | public function __construct($name, array $columns, callable $viewBuilder = null) 66 | { 67 | $this->name = $name; 68 | $this->viewBuilder = $viewBuilder; 69 | 70 | foreach ($columns as $column) { 71 | if (!$column instanceof ColumnInterface) { 72 | throw new InvalidArgumentException( 73 | sprintf( 74 | 'All columns passed to Datagrid::__construct() must of instances of %s.', 75 | ColumnInterface::class 76 | ) 77 | ); 78 | } 79 | 80 | $this->columns[$column->getName()] = $column; 81 | } 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | public function getName(): string 88 | { 89 | return $this->name; 90 | } 91 | 92 | /** 93 | * {@inheritdoc} 94 | */ 95 | public function getColumn(string $name): ColumnInterface 96 | { 97 | if (!isset($this->columns[$name])) { 98 | throw new UnknownColumnException($name, $this); 99 | } 100 | 101 | return $this->columns[$name]; 102 | } 103 | 104 | /** 105 | * {@inheritdoc} 106 | */ 107 | public function getColumns(): array 108 | { 109 | return $this->columns; 110 | } 111 | 112 | /** 113 | * {@inheritdoc} 114 | */ 115 | public function hasColumn(string $name): bool 116 | { 117 | return isset($this->columns[$name]); 118 | } 119 | 120 | /** 121 | * {@inheritdoc} 122 | */ 123 | public function hasColumnType(string $type): bool 124 | { 125 | foreach ($this->columns as $column) { 126 | if ($column->getType()->getInnerType() instanceof $type) { 127 | return true; 128 | } 129 | } 130 | 131 | return false; 132 | } 133 | 134 | /** 135 | * {@inheritdoc} 136 | */ 137 | public function setData($data) 138 | { 139 | if (null !== $this->data) { 140 | throw new BadMethodCallException('Datagrid::setData() can only be called once.'); 141 | } 142 | 143 | if (!is_array($data) && !$data instanceof \Traversable) { 144 | throw new UnexpectedTypeException($data, ['array', 'Traversable']); 145 | } 146 | 147 | $this->data = $data; 148 | 149 | return $this; 150 | } 151 | 152 | /** 153 | * {@inheritdoc} 154 | */ 155 | public function getData() 156 | { 157 | return $this->data; 158 | } 159 | 160 | /** 161 | * {@inheritdoc} 162 | */ 163 | public function createView(): DatagridView 164 | { 165 | if (!isset($this->data)) { 166 | throw new BadMethodCallException( 167 | 'setDate() must be called before before you can create a view from the Datagrid.' 168 | ); 169 | } 170 | 171 | $blockName = $this->getName(); 172 | $uniqueBlockPrefix = '_'.$blockName; 173 | 174 | $view = new DatagridView($this); 175 | $view->vars = [ 176 | 'cache_key' => $uniqueBlockPrefix.'_datagrid', 177 | 'unique_block_prefix' => $uniqueBlockPrefix, 178 | 'block_prefixes' => ['datagrid', $uniqueBlockPrefix], 179 | // Vars for the row-view 180 | 'row_vars' => [ 181 | 'unique_block_prefix' => $uniqueBlockPrefix.'_row', 182 | 'block_prefixes' => ['datagrid_row', $uniqueBlockPrefix.'_row'], 183 | ], 184 | ]; 185 | 186 | if (null !== $this->viewBuilder) { 187 | $builder = $this->viewBuilder; 188 | $builder($view); 189 | } 190 | 191 | $view->init($this); 192 | 193 | return $view; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/DatagridBuilder.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 17 | use Rollerworks\Component\Datagrid\Exception\InvalidArgumentException; 18 | use Rollerworks\Component\Datagrid\Util\CompoundColumnBuilder; 19 | use Rollerworks\Component\Datagrid\Util\CompoundColumnBuilderInterface; 20 | 21 | final class DatagridBuilder implements DatagridBuilderInterface 22 | { 23 | private $factory; 24 | private $columns = []; 25 | private $unresolvedColumns = []; 26 | private $viewBuilder; 27 | 28 | public function __construct(DatagridFactoryInterface $factory) 29 | { 30 | $this->factory = $factory; 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function add(string $name, string $type = null, array $options = []) 37 | { 38 | unset($this->columns[$name]); 39 | $this->unresolvedColumns[$name] = [ 40 | 'type' => $type, 41 | 'options' => $options, 42 | ]; 43 | 44 | return $this; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function set(ColumnInterface $column) 51 | { 52 | $this->columns[$column->getName()] = $column; 53 | unset($this->unresolvedColumns[$column->getName()]); 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function createCompound(string $name, array $options = [], string $type = null): CompoundColumnBuilderInterface 62 | { 63 | return new CompoundColumnBuilder($this->factory, $this, $name, $options, $type); 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function remove(string $name) 70 | { 71 | unset($this->columns[$name], $this->unresolvedColumns[$name]); 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | */ 79 | public function has(string $name): bool 80 | { 81 | if (isset($this->unresolvedColumns[$name])) { 82 | return true; 83 | } 84 | 85 | if (isset($this->columns[$name])) { 86 | return true; 87 | } 88 | 89 | return false; 90 | } 91 | 92 | /** 93 | * {@inheritdoc} 94 | */ 95 | public function get(string $name): ColumnInterface 96 | { 97 | if (isset($this->unresolvedColumns[$name])) { 98 | $this->columns[$name] = $this->factory->createColumn( 99 | $name, 100 | $this->unresolvedColumns[$name]['type'], 101 | $this->unresolvedColumns[$name]['options'] 102 | ); 103 | 104 | unset($this->unresolvedColumns[$name]); 105 | 106 | return $this->columns[$name]; 107 | } 108 | 109 | if (isset($this->columns[$name])) { 110 | return $this->columns[$name]; 111 | } 112 | 113 | throw new InvalidArgumentException(sprintf('Column with the name "%s" is not set on the builder.', $name)); 114 | } 115 | 116 | /** 117 | * {@inheritdoc} 118 | */ 119 | public function setDatagridViewBuilder(callable $builder = null): DatagridBuilderInterface 120 | { 121 | $this->viewBuilder = $builder; 122 | 123 | return $this; 124 | } 125 | 126 | /** 127 | * {@inheritdoc} 128 | */ 129 | public function getDatagridViewBuilder() 130 | { 131 | return $this->viewBuilder; 132 | } 133 | 134 | /** 135 | * {@inheritdoc} 136 | */ 137 | public function getDatagrid(string $name): DatagridInterface 138 | { 139 | foreach ($this->unresolvedColumns as $n => $column) { 140 | $this->columns[$n] = $this->factory->createColumn( 141 | $n, 142 | $column['type'], 143 | $column['options'] 144 | ); 145 | 146 | unset($this->unresolvedColumns[$n]); 147 | } 148 | 149 | return new Datagrid($name, $this->columns, $this->viewBuilder); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/DatagridBuilderInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 17 | use Rollerworks\Component\Datagrid\Util\CompoundColumnBuilderInterface; 18 | 19 | interface DatagridBuilderInterface 20 | { 21 | /** 22 | * Add a column to the builder. 23 | * 24 | * @param string $name 25 | * @param string $type 26 | * @param array $options 27 | * 28 | * @return self 29 | */ 30 | public function add(string $name, string $type = null, array $options = []); 31 | 32 | /** 33 | * Add a column instance to the builder. 34 | * 35 | * @param ColumnInterface $column 36 | * 37 | * @return DatagridBuilderInterface 38 | */ 39 | public function set(ColumnInterface $column); 40 | 41 | /** 42 | * Create a new CompoundColumnBuilder object. 43 | * 44 | * A CompoundColumn allows to group multiple columns together, 45 | * eg. one more more date value or one or more row actions. 46 | * 47 | * 48 | * createCompound('actions', ['label' => 'Actions', 'data_provider' => function ($data) {return ['id' => $data->id();}]) 49 | * ->add('edit', ActionType::class, ['data_provider' => '[id]', 'url_schema' => '/users/{id}/edit']) 50 | * ->add('delete', ActionType::class, ['data_provider' => '[id]', 'url_schema' => '/users/{id}/edit']) 51 | * ->end() // This registers the CompoundColumn at the DatagridBuilder, and return the DatagridBuilder. 52 | * 53 | * 54 | * @param string $name Name of this CompoundColumn 55 | * @param array $options Options for the CompoundColumn 56 | * @param string $type Optional type, must be a child-type of CompoundColumnType 57 | * 58 | * @return CompoundColumnBuilderInterface 59 | */ 60 | public function createCompound(string $name, array $options = [], string $type = null): CompoundColumnBuilderInterface; 61 | 62 | /** 63 | * Remove a column from the builder. 64 | * 65 | * @param string $name 66 | * 67 | * @return self 68 | */ 69 | public function remove(string $name); 70 | 71 | /** 72 | * Returns whether the builder has a column with the name. 73 | * 74 | * @param string $name 75 | * 76 | * @return bool 77 | */ 78 | public function has(string $name): bool; 79 | 80 | /** 81 | * Get the registered column by name. 82 | * 83 | * @param string $name 84 | * 85 | * @return ColumnInterface 86 | */ 87 | public function get(string $name): ColumnInterface; 88 | 89 | /** 90 | * Set DatagridView builder for the datagrid. 91 | * 92 | * @param callable|null $builder 93 | * 94 | * @return self 95 | */ 96 | public function setDatagridViewBuilder(callable $builder = null): DatagridBuilderInterface; 97 | 98 | /** 99 | * Get the DatagridView builder that was set for this builder. 100 | * 101 | * @return callable|null 102 | */ 103 | public function getDatagridViewBuilder(); 104 | 105 | /** 106 | * Return the configured datagrid instance. 107 | * 108 | * @param string $name 109 | * 110 | * @return DatagridInterface 111 | */ 112 | public function getDatagrid(string $name): DatagridInterface; 113 | } 114 | -------------------------------------------------------------------------------- /src/DatagridConfiguratorInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | /** 17 | * A DatagridConfigurator configures a DatagridBuilder instance. 18 | * 19 | * The purpose of a configurator is to allow re-usage of a Datagrid. 20 | * When you want to combine configurators, simply use PHP inheritance and 21 | * traits. 22 | * 23 | * @author Sebastiaan Stok 24 | */ 25 | interface DatagridConfiguratorInterface 26 | { 27 | public function buildDatagrid(DatagridBuilderInterface $builder, array $options); 28 | } 29 | -------------------------------------------------------------------------------- /src/DatagridExtensionInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Column\ColumnTypeExtensionInterface; 17 | use Rollerworks\Component\Datagrid\Column\ColumnTypeInterface; 18 | use Rollerworks\Component\Datagrid\Exception\DatagridException; 19 | 20 | /** 21 | * @author Sebastiaan Stok 22 | */ 23 | interface DatagridExtensionInterface 24 | { 25 | /** 26 | * Check if extension has column type of $type. 27 | * 28 | * @param string $name 29 | * 30 | * @return bool 31 | */ 32 | public function hasType(string $name): bool; 33 | 34 | /** 35 | * Get column type. 36 | * 37 | * @param string $name 38 | * 39 | * @throws DatagridException When the given column type is not provided by this extension 40 | * 41 | * @return ColumnTypeInterface 42 | */ 43 | public function getType(string $name): ColumnTypeInterface; 44 | 45 | /** 46 | * Check if extension has any column type extension for column of $type. 47 | * 48 | * @param string $type 49 | * 50 | * @return bool 51 | */ 52 | public function hasTypeExtensions(string $type): bool; 53 | 54 | /** 55 | * Return extensions for column type provided by this data grid extension. 56 | * 57 | * @param string $type 58 | * 59 | * @return ColumnTypeExtensionInterface[] 60 | */ 61 | public function getTypeExtensions(string $type): array; 62 | } 63 | -------------------------------------------------------------------------------- /src/DatagridFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 17 | use Rollerworks\Component\Datagrid\Column\ColumnTypeRegistryInterface; 18 | use Rollerworks\Component\Datagrid\Util\StringUtil; 19 | 20 | /** 21 | * @author Sebastiaan Stok 22 | */ 23 | class DatagridFactory implements DatagridFactoryInterface 24 | { 25 | private $typeRegistry; 26 | private $datagridRegistry; 27 | 28 | public function __construct(ColumnTypeRegistryInterface $typeRegistry, DatagridRegistryInterface $datagridRegistry) 29 | { 30 | $this->typeRegistry = $typeRegistry; 31 | $this->datagridRegistry = $datagridRegistry; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function createDatagrid($configurator, string $name = null, array $options = []): DatagridInterface 38 | { 39 | if (!$configurator instanceof DatagridConfiguratorInterface) { 40 | $configurator = $this->datagridRegistry->getConfigurator($configurator); 41 | } 42 | 43 | if (null === $name) { 44 | $name = StringUtil::fqcnToBlockPrefix(get_class($configurator)); 45 | } 46 | 47 | $builder = $this->createDatagridBuilder(); 48 | $configurator->buildDatagrid($builder, $options); 49 | 50 | return $builder->getDatagrid($name); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function createDatagridBuilder(): DatagridBuilderInterface 57 | { 58 | return new DatagridBuilder($this); 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function createColumn(string $name, string $type, array $options = []): ColumnInterface 65 | { 66 | $type = $this->typeRegistry->getType($type); 67 | 68 | $column = $type->createColumn($name, $options); 69 | 70 | // Explicitly call buildType() in order to be able to override either 71 | // createColumn() or buildType() in the resolved column type. 72 | $type->buildType($column, $column->getOptions()); 73 | 74 | return $column; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/DatagridFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 17 | 18 | /** 19 | * @author Sebastiaan Stok 20 | */ 21 | interface DatagridFactoryInterface 22 | { 23 | /** 24 | * Create a new ColumnInterface instance. 25 | * 26 | * @param string $name 27 | * @param string $type 28 | * @param array $options 29 | * 30 | * @return ColumnInterface 31 | */ 32 | public function createColumn(string $name, string $type, array $options = []): ColumnInterface; 33 | 34 | /** 35 | * Create a new DatagridInterface instance with a unique name. 36 | * 37 | * @param string|DatagridConfiguratorInterface $configurator Configurator for building the datagrid, 38 | * a string will be resolved to a configurator 39 | * @param string $name Name of the datagrid, 40 | * defaults to the simple name of the configurator 41 | * @param array $options Additional options for the configurator 42 | * 43 | * @return DatagridInterface 44 | */ 45 | public function createDatagrid($configurator, string $name = null, array $options = []): DatagridInterface; 46 | 47 | /** 48 | * Create a new DatagridBuilderInterface instance. 49 | * 50 | * @return DatagridBuilderInterface 51 | */ 52 | public function createDatagridBuilder(): DatagridBuilderInterface; 53 | } 54 | -------------------------------------------------------------------------------- /src/DatagridInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 17 | use Rollerworks\Component\Datagrid\Exception\BadMethodCallException; 18 | use Rollerworks\Component\Datagrid\Exception\UnknownColumnException; 19 | 20 | /** 21 | * @author Sebastiaan Stok 22 | * @author FSi sp. z o.o. 23 | */ 24 | interface DatagridInterface 25 | { 26 | /** 27 | * Returns the datagrid name. 28 | * 29 | * @return string 30 | */ 31 | public function getName(): string; 32 | 33 | /** 34 | * Return column with by name. 35 | * 36 | * @param string $name 37 | * 38 | * @throws UnknownColumnException when the column is not registered in the datagrid 39 | * 40 | * @return ColumnInterface 41 | */ 42 | public function getColumn(string $name): ColumnInterface; 43 | 44 | /** 45 | * Return all registered columns in the datagrid. 46 | * 47 | * @return ColumnInterface[] 48 | */ 49 | public function getColumns(): array; 50 | 51 | /** 52 | * Get if the column is registered on the datagrid. 53 | * 54 | * @param string $name 55 | * 56 | * @return bool 57 | */ 58 | public function hasColumn(string $name): bool; 59 | 60 | /** 61 | * Get whether column with a specific type is registered 62 | * on the grid. 63 | * 64 | * @param string $type 65 | * 66 | * @return bool 67 | */ 68 | public function hasColumnType(string $type): bool; 69 | 70 | /** 71 | * Create a new DatagridView object for rendering the datagrid. 72 | * 73 | * The created DatagridView should be passed to a compatible 74 | * datagrid renderer. 75 | * 76 | * @return DatagridView 77 | */ 78 | public function createView(): DatagridView; 79 | 80 | /** 81 | * Set the data collection of the datagrid. 82 | * 83 | * This method should only be called once and throw an exception 84 | * when called more then once. 85 | * 86 | * Data must be passed as an array or object that implements the 87 | * \ArrayAccess, \Countable and \IteratorAggregate interfaces. 88 | * 89 | * @param array|\Traversable $data 90 | * 91 | * @throws BadMethodCallException When data was already set 92 | */ 93 | public function setData($data); 94 | 95 | /** 96 | * Returns the data collection of the datagrid. 97 | * 98 | * @return array|\Traversable|null Returns null when no data was set 99 | */ 100 | public function getData(); 101 | } 102 | -------------------------------------------------------------------------------- /src/DatagridRegistry.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Exception\InvalidArgumentException; 17 | use Rollerworks\Component\Datagrid\Exception\UnexpectedTypeException; 18 | 19 | /** 20 | * @author Sebastiaan Stok 21 | */ 22 | class DatagridRegistry implements DatagridRegistryInterface 23 | { 24 | private $configurators; 25 | 26 | /** @var \Closure[] */ 27 | private $lazyConfigurators = []; 28 | 29 | /** 30 | * Constructor. 31 | * 32 | * Note you don't have to register the Configurator when it has no constructor 33 | * or setter dependencies. You can simple use the FQCN of the configurator class, 34 | * and will be initialized upon first usage. 35 | * 36 | * @param \Closure[] $configurators An array of lazy loading configurators. 37 | * The Closure when called it expected to return 38 | * the DatagridConfiguratorInterface object 39 | * 40 | * @throws UnexpectedTypeException 41 | */ 42 | public function __construct(array $configurators = []) 43 | { 44 | /** @var string $name */ 45 | foreach ($configurators as $name => $configurator) { 46 | if (!$configurator instanceof \Closure) { 47 | throw new UnexpectedTypeException($configurator, \Closure::class); 48 | } 49 | 50 | $this->lazyConfigurators[$name] = $configurator; 51 | } 52 | } 53 | 54 | /** 55 | * Returns a DatagridConfigurator by name. 56 | * 57 | * @param string $name The name of the datagrid configurator 58 | * 59 | * @throws InvalidArgumentException if the configurator can not be retrieved 60 | * 61 | * @return DatagridConfiguratorInterface 62 | */ 63 | public function getConfigurator(string $name): DatagridConfiguratorInterface 64 | { 65 | if (!isset($this->configurators[$name])) { 66 | $configurator = null; 67 | 68 | if (isset($this->lazyConfigurators[$name])) { 69 | $configurator = $this->lazyConfigurators[$name](); 70 | } 71 | 72 | if (!$configurator) { 73 | // Support fully-qualified class names. 74 | if (!class_exists($name) || !in_array(DatagridConfiguratorInterface::class, class_implements($name), true)) { 75 | throw new InvalidArgumentException(sprintf('Could not load datagrid configurator "%s"', $name)); 76 | } 77 | 78 | $configurator = new $name(); 79 | } 80 | 81 | $this->configurators[$name] = $configurator; 82 | } 83 | 84 | return $this->configurators[$name]; 85 | } 86 | 87 | /** 88 | * Returns whether the given DatagridConfigurator is supported. 89 | * 90 | * @param string $name The name of the datagrid configurator 91 | * 92 | * @return bool 93 | */ 94 | public function hasConfigurator(string $name): bool 95 | { 96 | if (isset($this->configurators[$name])) { 97 | return true; 98 | } 99 | 100 | if (isset($this->lazyConfigurators[$name])) { 101 | return true; 102 | } 103 | 104 | return class_exists($name) && in_array(DatagridConfiguratorInterface::class, class_implements($name), true); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/DatagridRegistryInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Exception\InvalidArgumentException; 17 | 18 | /** 19 | * @author Sebastiaan Stok 20 | */ 21 | interface DatagridRegistryInterface 22 | { 23 | /** 24 | * Returns a DatagridConfigurator by name. 25 | * 26 | * @param string $name The name of the datagrid configurator 27 | * 28 | * @throws InvalidArgumentException if the configurator can not be retrieved 29 | * 30 | * @return DatagridConfiguratorInterface 31 | */ 32 | public function getConfigurator(string $name): DatagridConfiguratorInterface; 33 | 34 | /** 35 | * Returns whether the given DatagridConfigurator is supported. 36 | * 37 | * @param string $name The name of the datagrid configurator 38 | * 39 | * @return bool 40 | */ 41 | public function hasConfigurator(string $name): bool; 42 | } 43 | -------------------------------------------------------------------------------- /src/DatagridRowView.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Column\CellView; 17 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 18 | 19 | /** 20 | * @author Sebastiaan Stok 21 | */ 22 | class DatagridRowView extends BaseView implements \IteratorAggregate, \Countable 23 | { 24 | /** 25 | * READ-ONLY: Cells views. 26 | * 27 | * @var CellView[] 28 | */ 29 | public $cells = []; 30 | 31 | /** 32 | * READ-ONLY: The source object from which view is created. 33 | * 34 | * @var array|object 35 | */ 36 | public $source; 37 | 38 | /** 39 | * READ-ONLY: Row index as given by the Datagrid. 40 | * 41 | * @var int 42 | */ 43 | public $index; 44 | 45 | /** 46 | * READ-ONLY: DatagridView this row is part of. 47 | * 48 | * @var DatagridView 49 | */ 50 | public $datagrid; 51 | 52 | /** 53 | * Constructor. 54 | * 55 | * @param DatagridView $datagridView 56 | * @param ColumnInterface[] $columns 57 | * @param mixed $source 58 | * @param int $index 59 | */ 60 | public function __construct(DatagridView $datagridView, array $columns, $source, int $index) 61 | { 62 | $this->datagrid = $datagridView; 63 | $this->source = $source; 64 | $this->index = $index; 65 | 66 | foreach ($columns as $column) { 67 | $name = $column->getName(); 68 | 69 | $this->cells[$name] = $column->createCellView($datagridView->columns[$name], $source, $index); 70 | } 71 | } 72 | 73 | /** 74 | * Returns the number of cells in the row. 75 | * 76 | * Implementation of Countable::count() 77 | * 78 | * @return int 79 | */ 80 | public function count(): int 81 | { 82 | return count($this->cells); 83 | } 84 | 85 | /** 86 | * Returns an iterator for the cells. 87 | * 88 | * @return \ArrayIterator The iterator 89 | */ 90 | public function getIterator() 91 | { 92 | return new \ArrayIterator($this->cells); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/DatagridView.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Column\HeaderView; 17 | use Rollerworks\Component\Datagrid\Exception\InvalidArgumentException; 18 | 19 | /** 20 | * DatagridView provides all the information to render a Datagrid. 21 | * 22 | * This class should only be initialized be directly. 23 | * 24 | * @author Sebastiaan Stok 25 | */ 26 | class DatagridView extends BaseView implements \IteratorAggregate, \Countable 27 | { 28 | /** 29 | * READ-ONLY: The Datagrid name. 30 | * 31 | * @var string 32 | */ 33 | public $name; 34 | 35 | /** 36 | * READ-ONLY: The Datagrid Header views. 37 | * 38 | * @var HeaderView[] 39 | */ 40 | public $columns = []; 41 | 42 | /** 43 | * READ-ONLY: The Datagrid rows. 44 | * 45 | * @var DatagridRowView[] 46 | */ 47 | public $rows = []; 48 | 49 | public function __construct(DatagridInterface $datagrid) 50 | { 51 | $this->name = $datagrid->getName(); 52 | } 53 | 54 | /** 55 | * Initialize the datagrid view. 56 | */ 57 | public function init(DatagridInterface $datagrid) 58 | { 59 | if (null === $data = $datagrid->getData()) { 60 | throw new InvalidArgumentException('No data provided for the view.'); 61 | } 62 | 63 | $columns = $datagrid->getColumns(); 64 | 65 | foreach ($columns as $column) { 66 | $this->columns[$column->getName()] = $column->createHeaderView($this); 67 | } 68 | 69 | if (!isset($this->vars['row_vars'])) { 70 | $this->vars['row_vars'] = []; 71 | } 72 | 73 | foreach ($data as $id => $value) { 74 | $this->rows[$id] = $row = new DatagridRowView($this, $columns, $value, $id); 75 | $row->vars = $this->vars['row_vars']; 76 | } 77 | } 78 | 79 | public function hasColumn(string $name): bool 80 | { 81 | return isset($this->columns[$name]); 82 | } 83 | 84 | public function getColumn(string $name): HeaderView 85 | { 86 | if (isset($this->columns[$name])) { 87 | return $this->columns[$name]; 88 | } 89 | 90 | throw new InvalidArgumentException(sprintf('Column "%s" does not exist in datagrid.', $name)); 91 | } 92 | 93 | /** 94 | * Returns the number of elements in the collection. 95 | * 96 | * @return int 97 | */ 98 | public function count() 99 | { 100 | return count($this->rows); 101 | } 102 | 103 | /** 104 | * Returns an iterator to iterate over rows (implements \IteratorAggregate). 105 | * 106 | * @return \ArrayIterator The iterator 107 | */ 108 | public function getIterator() 109 | { 110 | return new \ArrayIterator($this->rows); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Datagrids.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Extension\Core\CoreExtension; 17 | use Rollerworks\Component\Datagrid\Util\DatagridFactoryBuilder; 18 | 19 | /** 20 | * Entry point of the Datagrid system. 21 | * 22 | * *You should not use this class when integrating the Datagrid system 23 | * with a Framework that supports Dependency Injection.* 24 | * 25 | * Use this class to conveniently create new datagrid factories: 26 | * 27 | * 28 | * use Rollerworks\Component\Datagrid\Datagrids; 29 | * use Rollerworks\Component\Datagrid\Extension\Core\Type as ColumnType; 30 | * 31 | * $datagridFactory = Datagrids::createDatagridFactory(); 32 | * 33 | * $datagrid = $datagridFactory->createDatagridBuilder() 34 | * ->add('id', ColumnType\NumberType::class) 35 | * ->add('username', ColumnType\TextType::class) 36 | * ->add('registered_on', ColumnType\DateTimeType::class) 37 | * ->add('enabled', ColumnType\BooleanType::class, ['true_value' => 'Yes', 'false_value' => 'No']) 38 | * ->getDatagrid('users_datagrid') 39 | * ; 40 | * 41 | * 42 | * You can also add custom extensions to the datagrid factory: 43 | * 44 | * 45 | * $datagridFactory = Datagrids::createDatagridFactoryBuilder(); 46 | * ->addExtension(new AcmeExtension()) 47 | * ->getDatagridFactory() 48 | * ; 49 | * 50 | * 51 | * If you create custom types, it is not required to register them 52 | * as they will be be automatically loaded by there FQCN 53 | * `Acme\Datagrid\Type\PhoneNumberType`. 54 | * 55 | * But when they have external dependencies you need to register them 56 | * manually. It's recommended to create your own extensions that lazily 57 | * loads these types and type extensions. 58 | * 59 | * When there are no performance problems, you can also pass them directly 60 | * to the datagrid factory: 61 | * 62 | * 63 | * use Rollerworks\Component\Datagrid\Datagrids; 64 | * 65 | * $datagridFactory = Datagrids::createDatagridFactoryBuilder(); 66 | * ->addType(new PhoneNumberType()) 67 | * ->addTypeExtension(new GedmoExtension()) 68 | * ->getDatagridFactory() 69 | * ; 70 | * 71 | * 72 | * **Note:** Type extensions are not loaded automatically, you must always register them. 73 | * 74 | * @author Sebastiaan Stok 75 | */ 76 | final class Datagrids 77 | { 78 | /** 79 | * Creates a DatagridFactory builder with the default configuration. 80 | * 81 | * @return DatagridFactoryBuilder 82 | */ 83 | public static function createDatagridFactoryBuilder(): DatagridFactoryBuilder 84 | { 85 | $builder = new DatagridFactoryBuilder(); 86 | $builder->addExtension(new CoreExtension()); 87 | 88 | return $builder; 89 | } 90 | 91 | /** 92 | * @return DatagridFactory 93 | */ 94 | public static function createDatagridFactory(): DatagridFactory 95 | { 96 | $builder = new DatagridFactoryBuilder(); 97 | $builder->addExtension(new CoreExtension()); 98 | 99 | return $builder->getDatagridFactory(); 100 | } 101 | 102 | /** 103 | * This class cannot be instantiated. 104 | */ 105 | private function __construct() 106 | { 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Exception/BadMethodCallException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Exception; 15 | 16 | /** 17 | * Base BadMethodCallException for the Datagrid component. 18 | */ 19 | class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/DataMappingException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Exception; 15 | 16 | class DataMappingException extends \RuntimeException implements ExceptionInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /src/Exception/DataProviderException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Exception; 15 | 16 | use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException; 17 | use Symfony\Component\PropertyAccess\PropertyPath; 18 | 19 | final class DataProviderException extends \RuntimeException implements ExceptionInterface 20 | { 21 | public static function autoAccessorUnableToGetValue(string $columnName) 22 | { 23 | return new self( 24 | sprintf('Unable to get value for column "%s". Consider setting the "data_provider" option.', $columnName) 25 | ); 26 | } 27 | 28 | public static function pathAccessorUnableToGetValue(string $columnName, PropertyPath $propertyPath) 29 | { 30 | return new self( 31 | sprintf('Unable to get value for column "%s" with property-path "%s".', $columnName, (string) $propertyPath) 32 | ); 33 | } 34 | 35 | public static function invalidPropertyPath(string $columnName, InvalidPropertyPathException $e) 36 | { 37 | return new self( 38 | sprintf('Invalid property-path for column "%s" with message: %s', $columnName, $e->getMessage()), 39 | 1, 40 | $e 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Exception/DatagridColumnException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Exception; 15 | 16 | class DatagridColumnException extends DatagridException 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /src/Exception/DatagridException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Exception; 15 | 16 | class DatagridException extends \Exception implements ExceptionInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /src/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Exception; 15 | 16 | /** 17 | * @author Sebastiaan Stok 18 | */ 19 | interface ExceptionInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Exception; 15 | 16 | /** 17 | * @author Sebastiaan Stok 18 | */ 19 | class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/InvalidConfigurationException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Exception; 15 | 16 | /** 17 | * @author Sebastiaan Stok 18 | */ 19 | class InvalidConfigurationException extends InvalidArgumentException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/LogicException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Exception; 15 | 16 | class LogicException extends \LogicException implements ExceptionInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /src/Exception/TransformationFailedException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Exception; 15 | 16 | /** 17 | * @author Sebastiaan Stok 18 | */ 19 | class TransformationFailedException extends \RuntimeException implements ExceptionInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/UnexpectedTypeException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Exception; 15 | 16 | class UnexpectedTypeException extends DatagridException 17 | { 18 | /** 19 | * @param mixed $value 20 | * @param string|array $expectedType 21 | */ 22 | public function __construct($value, $expectedType) 23 | { 24 | if (is_array($expectedType)) { 25 | $expectedType = implode('", "', $expectedType); 26 | } 27 | 28 | parent::__construct( 29 | sprintf( 30 | 'Expected argument of type "%s", "%s" given', 31 | $expectedType, 32 | is_object($value) ? get_class($value) : gettype($value) 33 | ) 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Exception/UnknownColumnException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Exception; 15 | 16 | use Rollerworks\Component\Datagrid\DatagridInterface; 17 | 18 | /** 19 | * @author Sebastiaan Stok 20 | */ 21 | class UnknownColumnException extends \InvalidArgumentException implements ExceptionInterface 22 | { 23 | /** 24 | * @var DatagridInterface 25 | */ 26 | private $datagrid; 27 | 28 | /** 29 | * @var string 30 | */ 31 | private $columnName; 32 | 33 | /** 34 | * @param string $columnName 35 | * @param DatagridInterface $datagrid 36 | */ 37 | public function __construct($columnName, DatagridInterface $datagrid) 38 | { 39 | $this->datagrid = $datagrid; 40 | $this->columnName = $columnName; 41 | 42 | parent::__construct( 43 | sprintf('Column "%s" is not registered in Datagrid "%s".', $columnName, $datagrid->getName()) 44 | ); 45 | } 46 | 47 | /** 48 | * @return DatagridInterface 49 | */ 50 | public function getDatagrid(): DatagridInterface 51 | { 52 | return $this->datagrid; 53 | } 54 | 55 | /** 56 | * @return string 57 | */ 58 | public function getColumnName(): string 59 | { 60 | return $this->columnName; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Extension/Core/CoreExtension.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core; 15 | 16 | use Rollerworks\Component\Datagrid\AbstractDatagridExtension; 17 | use Symfony\Component\PropertyAccess\PropertyAccess; 18 | 19 | /** 20 | * @author Sebastiaan Stok 21 | */ 22 | class CoreExtension extends AbstractDatagridExtension 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | protected function loadTypes(): array 28 | { 29 | $propertyAccessor = PropertyAccess::createPropertyAccessor(); 30 | 31 | return [ 32 | new Type\ColumnType($propertyAccessor), 33 | new Type\CompoundColumnType(), 34 | 35 | new Type\ActionType(), 36 | new Type\BatchType(), 37 | new Type\BooleanType(), 38 | new Type\DateTimeType(), 39 | new Type\MoneyType(), 40 | new Type\NumberType(), 41 | new Type\TextType(), 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Extension/Core/DataTransformer/BaseDateTimeTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\DataTransformer; 15 | 16 | use Rollerworks\Component\Datagrid\DataTransformerInterface; 17 | 18 | /** 19 | * @author Bernhard Schussek 20 | * @author Florian Eckerstorfer 21 | */ 22 | abstract class BaseDateTimeTransformer implements DataTransformerInterface 23 | { 24 | /** 25 | * @var array 26 | */ 27 | protected static $formats = [ 28 | \IntlDateFormatter::NONE, 29 | \IntlDateFormatter::FULL, 30 | \IntlDateFormatter::LONG, 31 | \IntlDateFormatter::MEDIUM, 32 | \IntlDateFormatter::SHORT, 33 | ]; 34 | 35 | /** 36 | * @var string 37 | */ 38 | protected $inputTimezone; 39 | 40 | /** 41 | * @var string 42 | */ 43 | protected $outputTimezone; 44 | 45 | /** 46 | * Constructor. 47 | * 48 | * @param string $inputTimezone The name of the input timezone 49 | * @param string $outputTimezone The name of the output timezone 50 | */ 51 | public function __construct(string $inputTimezone = null, string $outputTimezone = null) 52 | { 53 | $this->inputTimezone = $inputTimezone ?: date_default_timezone_get(); 54 | $this->outputTimezone = $outputTimezone ?: date_default_timezone_get(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Extension/Core/DataTransformer/ChainTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\DataTransformer; 15 | 16 | use Rollerworks\Component\Datagrid\DataTransformerInterface; 17 | 18 | /** 19 | * The ChainTransformer allows to combine multiple transformers. 20 | * 21 | * @author Sebastiaan Stok 22 | */ 23 | final class ChainTransformer implements DataTransformerInterface 24 | { 25 | /** 26 | * @var DataTransformerInterface[] 27 | */ 28 | private $transformers; 29 | 30 | /** 31 | * Constructor. 32 | * 33 | * @param DataTransformerInterface[] ...$transformers Transformers provided as variadic parameter 34 | */ 35 | public function __construct(DataTransformerInterface ...$transformers) 36 | { 37 | $this->transformers = $transformers; 38 | } 39 | 40 | public function append(DataTransformerInterface $transformer): self 41 | { 42 | $this->transformers[] = $transformer; 43 | 44 | return $this; 45 | } 46 | 47 | public function prepend(DataTransformerInterface $transformer): self 48 | { 49 | array_unshift($this->transformers, $transformer); 50 | 51 | return $this; 52 | } 53 | 54 | /** 55 | * @return DataTransformerInterface[] 56 | */ 57 | public function all(): array 58 | { 59 | return $this->transformers; 60 | } 61 | 62 | public function reset(): self 63 | { 64 | $this->transformers = []; 65 | 66 | return $this; 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | public function transform($value) 73 | { 74 | foreach ($this->transformers as $transformer) { 75 | $value = $transformer->transform($value); 76 | } 77 | 78 | return $value; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\DataTransformer; 15 | 16 | use Rollerworks\Component\Datagrid\Exception\TransformationFailedException; 17 | use Rollerworks\Component\Datagrid\Exception\UnexpectedTypeException; 18 | 19 | /** 20 | * Transforms between a normalized time and a localized time string. 21 | * 22 | * @author Bernhard Schussek 23 | * @author Florian Eckerstorfer 24 | */ 25 | class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer 26 | { 27 | private $dateFormat; 28 | private $timeFormat; 29 | private $pattern; 30 | private $calendar; 31 | 32 | /** 33 | * Constructor. 34 | * 35 | * @see BaseDateTimeTransformer::formats for available format options 36 | * 37 | * @param string $inputTimezone The name of the input timezone 38 | * @param string $outputTimezone The name of the output timezone 39 | * @param int $dateFormat The date format 40 | * @param int $timeFormat The time format 41 | * @param int $calendar One of the \IntlDateFormatter calendar constants 42 | * @param string $pattern A pattern to pass to \IntlDateFormatter 43 | * 44 | * @throws UnexpectedTypeException If a format is not supported or if a timezone is not a string 45 | */ 46 | public function __construct( 47 | string $inputTimezone = null, 48 | string $outputTimezone = null, 49 | int $dateFormat = null, 50 | int $timeFormat = null, 51 | int $calendar = \IntlDateFormatter::GREGORIAN, 52 | string $pattern = null 53 | ) { 54 | parent::__construct($inputTimezone, $outputTimezone); 55 | 56 | if (null === $dateFormat) { 57 | $dateFormat = \IntlDateFormatter::MEDIUM; 58 | } 59 | 60 | if (null === $timeFormat) { 61 | $timeFormat = \IntlDateFormatter::SHORT; 62 | } 63 | 64 | if (!in_array($dateFormat, self::$formats, true)) { 65 | throw new UnexpectedTypeException($dateFormat, self::$formats); 66 | } 67 | 68 | if (!in_array($timeFormat, self::$formats, true)) { 69 | throw new UnexpectedTypeException($timeFormat, self::$formats); 70 | } 71 | 72 | $this->dateFormat = $dateFormat; 73 | $this->timeFormat = $timeFormat; 74 | $this->calendar = $calendar; 75 | $this->pattern = $pattern ?? ''; 76 | } 77 | 78 | /** 79 | * Transforms a normalized date into a localized date string. 80 | * 81 | * @param \DateTime $dateTime Normalized date 82 | * 83 | * @throws TransformationFailedException If the given value is not an instance 84 | * of \DateTime or if the date could not 85 | * be transformed 86 | * 87 | * @return string|array Localized date string/array 88 | */ 89 | public function transform($dateTime) 90 | { 91 | if (null === $dateTime) { 92 | return ''; 93 | } 94 | 95 | if (!$dateTime instanceof \DateTime) { 96 | throw new TransformationFailedException('Expected a \DateTime.'); 97 | } 98 | 99 | // convert time to UTC before passing it to the formatter 100 | $dateTime = clone $dateTime; 101 | 102 | if ('UTC' !== $this->inputTimezone) { 103 | $dateTime->setTimezone(new \DateTimeZone('UTC')); 104 | } 105 | 106 | $value = $this->getIntlDateFormatter()->format((int) $dateTime->format('U')); 107 | 108 | if (intl_get_error_code() != 0) { 109 | throw new TransformationFailedException(intl_get_error_message()); 110 | } 111 | 112 | return $value; 113 | } 114 | 115 | protected function getIntlDateFormatter(): \IntlDateFormatter 116 | { 117 | $intlDateFormatter = new \IntlDateFormatter( 118 | \Locale::getDefault(), 119 | $this->dateFormat, 120 | $this->timeFormat, 121 | $this->outputTimezone, 122 | $this->calendar, 123 | $this->pattern 124 | ); 125 | 126 | $intlDateFormatter->setLenient(false); 127 | 128 | // Ensure the year is always four digits when no pattern is set 129 | if ('' === $this->pattern) { 130 | $intlDateFormatter->setPattern(str_replace(['yy', 'yyyyyyyy'], 'yyyy', $intlDateFormatter->getPattern())); 131 | } 132 | 133 | return $intlDateFormatter; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Extension/Core/DataTransformer/EmptyValueTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\DataTransformer; 15 | 16 | use Rollerworks\Component\Datagrid\DataTransformerInterface; 17 | use Rollerworks\Component\Datagrid\Exception\TransformationFailedException; 18 | use Rollerworks\Component\Datagrid\Exception\UnexpectedTypeException; 19 | 20 | /** 21 | * @author Sebastiaan Stok 22 | */ 23 | final class EmptyValueTransformer implements DataTransformerInterface 24 | { 25 | /** 26 | * @var string|array 27 | */ 28 | private $emptyValue; 29 | 30 | /** 31 | * @param string|array $emptyValue 32 | * 33 | * @throws UnexpectedTypeException 34 | */ 35 | public function __construct($emptyValue = '') 36 | { 37 | if (!is_string($emptyValue) && !is_array($emptyValue)) { 38 | throw new UnexpectedTypeException($emptyValue, ['string', 'array']); 39 | } 40 | 41 | $this->emptyValue = $emptyValue; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function transform($value) 48 | { 49 | if (is_array($this->emptyValue) && !is_array($value)) { 50 | throw new TransformationFailedException('Empty value is an array, but provided value is not an array.'); 51 | } 52 | 53 | // Note. empty is not used here as 0 is a none-empty value. 54 | // Only false, null and an empty string is considered empty. 55 | 56 | if (!is_array($value)) { 57 | return '' === (string) $value ? $this->emptyValue : $value; 58 | } 59 | 60 | $emptyIsString = is_string($this->emptyValue); 61 | 62 | foreach ($value as $field => &$val) { 63 | if (isset($val) && '' !== $val) { 64 | continue; 65 | } 66 | 67 | if ($emptyIsString) { 68 | $val = $this->emptyValue; 69 | } elseif (!array_key_exists($field, $this->emptyValue)) { 70 | throw new TransformationFailedException( 71 | sprintf('No empty value-replacement for field "%s" set.', $field) 72 | ); 73 | } else { 74 | $emptyValue = $this->emptyValue[$field]; 75 | 76 | if (!is_string($emptyValue)) { 77 | throw new TransformationFailedException( 78 | sprintf('Empty value of field "%s" must be a string value.', $field) 79 | ); 80 | } 81 | 82 | $val = $emptyValue; 83 | } 84 | } 85 | 86 | return $value; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\DataTransformer; 15 | 16 | /** 17 | * Transforms between an integer and a localized number with grouping 18 | * (each thousand) and comma separators. 19 | * 20 | * @author Bernhard Schussek 21 | */ 22 | class IntegerToLocalizedStringTransformer extends NumberToLocalizedStringTransformer 23 | { 24 | /** 25 | * Constructs a transformer. 26 | * 27 | * @param bool $grouping Whether thousands should be grouped 28 | * @param int $roundingMode One of the ROUND_ constants in this class 29 | */ 30 | public function __construct(bool $grouping = null, int $roundingMode = null) 31 | { 32 | if (null === $roundingMode) { 33 | $roundingMode = self::ROUND_DOWN; 34 | } 35 | 36 | parent::__construct(0, $grouping, $roundingMode); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\DataTransformer; 15 | 16 | use Rollerworks\Component\Datagrid\Exception\TransformationFailedException; 17 | 18 | /** 19 | * Transforms between a normalized format and a localized money string. 20 | * 21 | * @author Sebastiaan Stok 22 | * @author Bernhard Schussek 23 | * @author Florian Eckerstorfer 24 | */ 25 | class MoneyToLocalizedStringTransformer extends NumberToLocalizedStringTransformer 26 | { 27 | /** 28 | * @var int 29 | */ 30 | private $divisor = 1; 31 | 32 | /** 33 | * @var string 34 | */ 35 | private $defaultCurrency; 36 | 37 | /** 38 | * Constructor. 39 | * 40 | * @param int $precision 41 | * @param bool $grouping 42 | * @param int $roundingMode 43 | * @param int $divisor 44 | * @param string $defaultCurrency 45 | */ 46 | public function __construct( 47 | int $precision = 2, 48 | bool $grouping = true, 49 | int $roundingMode = self::ROUND_HALF_UP, 50 | int $divisor = 1, 51 | string $defaultCurrency = 'EUR' 52 | ) { 53 | parent::__construct($precision, $grouping, $roundingMode); 54 | 55 | $this->divisor = $divisor; 56 | $this->defaultCurrency = $defaultCurrency; 57 | } 58 | 59 | /** 60 | * Transforms a normalized format into a localized money string. 61 | * 62 | * @param string|int|float|array $value 63 | * 64 | * @throws TransformationFailedException If the given value is not numeric or 65 | * if the value can not be transformed 66 | * 67 | * @return string Localized money string 68 | */ 69 | public function transform($value) 70 | { 71 | if (null === $value) { 72 | return ''; 73 | } 74 | 75 | if (is_array($value)) { 76 | return $this->doTransformation($value['currency'] ?: $this->defaultCurrency, (string) $value['amount']); 77 | } 78 | 79 | // Convert fixed spaces to normal ones. 80 | $value = str_replace("\xc2\xa0", ' ', (string) $value); 81 | 82 | if (false !== strpos($value, ' ')) { 83 | return $this->transformStringWithCurrencyToLocalized($value); 84 | } 85 | 86 | return $this->doTransformation($this->defaultCurrency, $value); 87 | } 88 | 89 | private function transformStringWithCurrencyToLocalized(string $value) 90 | { 91 | list($currency, $amount) = explode(' ', $value); 92 | 93 | if (mb_strlen($currency) != 3) { 94 | throw new TransformationFailedException( 95 | sprintf( 96 | 'Currency "%s" extracted from "%s" is not accepted. Only three character format is supported.', 97 | $currency, 98 | $value 99 | ) 100 | ); 101 | } 102 | 103 | return $this->doTransformation($currency, $amount); 104 | } 105 | 106 | private function doTransformation(string $currency, string $amount) 107 | { 108 | if (!is_numeric($amount)) { 109 | throw new TransformationFailedException('Expected a numeric value.'); 110 | } 111 | 112 | $amount /= $this->divisor; 113 | $formatter = $this->getNumberFormatter(\NumberFormatter::CURRENCY); 114 | 115 | $value = $formatter->formatCurrency($amount, $currency); 116 | 117 | if (intl_is_failure($formatter->getErrorCode())) { 118 | throw new TransformationFailedException($formatter->getErrorMessage()); 119 | } 120 | 121 | // Convert fixed spaces to normal ones 122 | return str_replace("\xc2\xa0", ' ', $value); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\DataTransformer; 15 | 16 | use Rollerworks\Component\Datagrid\DataTransformerInterface; 17 | use Rollerworks\Component\Datagrid\Exception\TransformationFailedException; 18 | 19 | /** 20 | * Transforms between a number type and a localized number with grouping 21 | * (each thousand) and comma separators. 22 | * 23 | * @author Bernhard Schussek 24 | * @author Florian Eckerstorfer 25 | */ 26 | class NumberToLocalizedStringTransformer implements DataTransformerInterface 27 | { 28 | /** 29 | * Rounds a number towards positive infinity. 30 | * 31 | * Rounds 1.4 to 2 and -1.4 to -1. 32 | */ 33 | const ROUND_CEILING = \NumberFormatter::ROUND_CEILING; 34 | 35 | /** 36 | * Rounds a number towards negative infinity. 37 | * 38 | * Rounds 1.4 to 1 and -1.4 to -2. 39 | */ 40 | const ROUND_FLOOR = \NumberFormatter::ROUND_FLOOR; 41 | 42 | /** 43 | * Rounds a number away from zero. 44 | * 45 | * Rounds 1.4 to 2 and -1.4 to -2. 46 | */ 47 | const ROUND_UP = \NumberFormatter::ROUND_UP; 48 | 49 | /** 50 | * Rounds a number towards zero. 51 | * 52 | * Rounds 1.4 to 1 and -1.4 to -1. 53 | */ 54 | const ROUND_DOWN = \NumberFormatter::ROUND_DOWN; 55 | 56 | /** 57 | * Rounds to the nearest number and halves to the next even number. 58 | * 59 | * Rounds 2.5, 1.6 and 1.5 to 2 and 1.4 to 1. 60 | */ 61 | const ROUND_HALF_EVEN = \NumberFormatter::ROUND_HALFEVEN; 62 | 63 | /** 64 | * Rounds to the nearest number and halves away from zero. 65 | * 66 | * Rounds 2.5 to 3, 1.6 and 1.5 to 2 and 1.4 to 1. 67 | */ 68 | const ROUND_HALF_UP = \NumberFormatter::ROUND_HALFUP; 69 | 70 | /** 71 | * Rounds to the nearest number and halves towards zero. 72 | * 73 | * Rounds 2.5 and 1.6 to 2, 1.5 and 1.4 to 1. 74 | */ 75 | const ROUND_HALF_DOWN = \NumberFormatter::ROUND_HALFDOWN; 76 | 77 | /** 78 | * @var int|null 79 | */ 80 | protected $precision; 81 | 82 | /** 83 | * @var bool|null 84 | */ 85 | protected $grouping; 86 | 87 | /** 88 | * @var int|null 89 | */ 90 | protected $roundingMode; 91 | 92 | /** 93 | * @var int 94 | */ 95 | protected $type; 96 | 97 | /** 98 | * @param int $precision 99 | * @param bool $grouping 100 | * @param int $roundingMode 101 | * @param int $type 102 | */ 103 | public function __construct($precision = null, $grouping = null, $roundingMode = null, $type = \NumberFormatter::TYPE_DOUBLE) 104 | { 105 | if (null === $grouping) { 106 | $grouping = false; 107 | } 108 | 109 | if (null === $roundingMode) { 110 | $roundingMode = self::ROUND_HALF_UP; 111 | } 112 | 113 | $this->precision = $precision; 114 | $this->grouping = $grouping; 115 | $this->roundingMode = $roundingMode; 116 | $this->type = $type; 117 | } 118 | 119 | /** 120 | * Transforms a number type into localized number. 121 | * 122 | * @param int|float $value Number value 123 | * 124 | * @throws TransformationFailedException If the given value is not numeric 125 | * or if the value can not be transformed 126 | * 127 | * @return string Localized value 128 | */ 129 | public function transform($value) 130 | { 131 | if (null === $value) { 132 | return ''; 133 | } 134 | 135 | if (!is_numeric($value)) { 136 | throw new TransformationFailedException('Expected a numeric.'); 137 | } 138 | 139 | $formatter = $this->getNumberFormatter(); 140 | $value = $formatter->format($value); 141 | 142 | if (intl_is_failure($formatter->getErrorCode())) { 143 | throw new TransformationFailedException($formatter->getErrorMessage()); 144 | } 145 | 146 | // Convert fixed spaces to normal ones 147 | $value = str_replace("\xc2\xa0", ' ', $value); 148 | 149 | return $value; 150 | } 151 | 152 | /** 153 | * Returns a preconfigured \NumberFormatter instance. 154 | * 155 | * @param int $type 156 | * 157 | * @return \NumberFormatter 158 | */ 159 | protected function getNumberFormatter($type = \NumberFormatter::DECIMAL) 160 | { 161 | $formatter = new \NumberFormatter(\Locale::getDefault(), $type); 162 | 163 | if (null !== $this->precision) { 164 | $formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->precision); 165 | $formatter->setAttribute(\NumberFormatter::ROUNDING_MODE, $this->roundingMode); 166 | } 167 | 168 | $formatter->setAttribute(\NumberFormatter::GROUPING_USED, $this->grouping); 169 | 170 | return $formatter; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/Extension/Core/DataTransformer/StringToDateTimeTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\DataTransformer; 15 | 16 | use Rollerworks\Component\Datagrid\Exception\TransformationFailedException; 17 | use Rollerworks\Component\Datagrid\Exception\UnexpectedTypeException; 18 | 19 | /** 20 | * Transforms between a date string and a DateTime object. 21 | * 22 | * @author Bernhard Schussek 23 | * @author Florian Eckerstorfer 24 | */ 25 | class StringToDateTimeTransformer extends BaseDateTimeTransformer 26 | { 27 | /** 28 | * Format used for parsing strings. 29 | * 30 | * Different than the {@link $generateFormat} because formats for parsing 31 | * support additional characters in PHP that are not supported for 32 | * generating strings. 33 | * 34 | * @var string 35 | */ 36 | private $parseFormat; 37 | 38 | /** 39 | * Transforms a \DateTime instance to a string. 40 | * 41 | * @see \DateTime::format() for supported formats 42 | * 43 | * @param string $inputTimezone The name of the input timezone 44 | * @param string $outputTimezone The name of the output timezone 45 | * @param string $format The date format 46 | * 47 | * @throws UnexpectedTypeException if a timezone is not a string 48 | */ 49 | public function __construct($inputTimezone = null, $outputTimezone = null, $format = 'Y-m-d H:i:s') 50 | { 51 | parent::__construct($inputTimezone, $outputTimezone); 52 | 53 | $this->parseFormat = $format; 54 | } 55 | 56 | /** 57 | * Transforms a date string in the configured timezone into a DateTime object. 58 | * 59 | * @param string $value A value as produced by PHP's date() function 60 | * 61 | * @throws TransformationFailedException If the given value is not a string, 62 | * if the date could not be parsed or 63 | * if the input timezone is not supported 64 | * 65 | * @return \DateTime|null An instance of \DateTime 66 | */ 67 | public function transform($value) 68 | { 69 | if (empty($value)) { 70 | return; 71 | } 72 | 73 | if (!is_string($value)) { 74 | throw new TransformationFailedException('Expected a string.'); 75 | } 76 | 77 | try { 78 | $outputTz = new \DateTimeZone($this->outputTimezone); 79 | $dateTime = \DateTime::createFromFormat($this->parseFormat, $value, $outputTz); 80 | 81 | $lastErrors = \DateTime::getLastErrors(); 82 | 83 | if (0 < $lastErrors['warning_count'] || 0 < $lastErrors['error_count']) { 84 | throw new TransformationFailedException( 85 | implode(', ', array_merge( 86 | array_values($lastErrors['warnings']), 87 | array_values($lastErrors['errors']) 88 | )) 89 | ); 90 | } 91 | 92 | if ($this->inputTimezone !== $this->outputTimezone) { 93 | $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); 94 | } 95 | } catch (TransformationFailedException $e) { 96 | throw $e; 97 | } catch (\Exception $e) { 98 | throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); 99 | } 100 | 101 | return $dateTime; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Extension/Core/DataTransformer/TimestampToDateTimeTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\DataTransformer; 15 | 16 | use Rollerworks\Component\Datagrid\Exception\TransformationFailedException; 17 | 18 | /** 19 | * Transforms between a timestamp and a DateTime object. 20 | * 21 | * @author Bernhard Schussek 22 | * @author Florian Eckerstorfer 23 | */ 24 | class TimestampToDateTimeTransformer extends BaseDateTimeTransformer 25 | { 26 | /** 27 | * Transforms a timestamp in the configured timezone into a DateTime object. 28 | * 29 | * @param string $value A timestamp 30 | * 31 | * @throws TransformationFailedException If the given value is not a timestamp 32 | * or if the given timestamp is invalid 33 | * 34 | * @return \DateTime|null 35 | */ 36 | public function transform($value) 37 | { 38 | if (null === $value) { 39 | return; 40 | } 41 | 42 | if (!is_numeric($value)) { 43 | throw new TransformationFailedException('Expected a numeric.'); 44 | } 45 | 46 | try { 47 | $dateTime = new \DateTime(); 48 | $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); 49 | $dateTime->setTimestamp((int) $value); 50 | 51 | if ($this->inputTimezone !== $this->outputTimezone) { 52 | $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); 53 | } 54 | } catch (\Exception $e) { 55 | throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); 56 | } 57 | 58 | return $dateTime; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Extension/Core/DataTransformer/ValueFormatTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\DataTransformer; 15 | 16 | use Rollerworks\Component\Datagrid\DataTransformerInterface; 17 | use Rollerworks\Component\Datagrid\Exception\TransformationFailedException; 18 | 19 | /** 20 | * @author Sebastiaan Stok 21 | */ 22 | class ValueFormatTransformer implements DataTransformerInterface 23 | { 24 | /** 25 | * @var null|string 26 | */ 27 | private $glue; 28 | 29 | /** 30 | * @var callback|null|string 31 | */ 32 | private $format; 33 | 34 | /** 35 | * @param null|string $glue 36 | * @param null|string|callback $format 37 | */ 38 | public function __construct($glue = null, $format = null) 39 | { 40 | $this->glue = $glue; 41 | $this->format = $format; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function transform($value) 48 | { 49 | if (is_array($value) && null === $this->glue && null === $this->format) { 50 | throw new TransformationFailedException( 51 | sprintf('At least "glue" or "format" option must be set when the end value is an array.') 52 | ); 53 | } 54 | 55 | if (null === $this->format) { 56 | // Glue is required to be set at this point, so no need for further checking. 57 | if (is_array($value)) { 58 | return implode($this->glue, $value); 59 | } 60 | 61 | return (string) $value; 62 | } 63 | 64 | if (!is_array($value)) { 65 | $format = $this->format; 66 | 67 | if (is_callable($format)) { 68 | return $format($value); 69 | } 70 | 71 | return sprintf($format, $value); 72 | } 73 | 74 | return $this->formatValue($value, $this->format, $this->glue); 75 | } 76 | 77 | private function formatValue($value, $format, $glue): string 78 | { 79 | $formatCallable = is_callable($format); 80 | 81 | if (null === $glue) { 82 | if ($formatCallable) { 83 | return $format($value); 84 | } 85 | 86 | return vsprintf($format, $value); 87 | } 88 | 89 | $formattedValues = []; 90 | 91 | foreach ($value as $field => $val) { 92 | if ($formatCallable) { 93 | $formattedValues[] = $format($val, $field); 94 | } else { 95 | $formattedValues[] = sprintf($format, $val); 96 | } 97 | } 98 | 99 | return implode($glue, $formattedValues); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Extension/Core/Type/ActionType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\Type; 15 | 16 | use Rollerworks\Component\Datagrid\Column\AbstractType; 17 | use Rollerworks\Component\Datagrid\Column\CellView; 18 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 19 | use Rollerworks\Component\Datagrid\Exception\InvalidConfigurationException; 20 | use Symfony\Component\OptionsResolver\Options; 21 | use Symfony\Component\OptionsResolver\OptionsResolver; 22 | 23 | /** 24 | * @author Sebastiaan Stok 25 | */ 26 | class ActionType extends AbstractType 27 | { 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function buildCellView(CellView $view, ColumnInterface $column, array $options) 32 | { 33 | $mappingValues = $view->value; 34 | 35 | if (is_object($options['content'])) { 36 | $options['content'] = $options['content']($mappingValues); 37 | } 38 | 39 | $view->vars['url'] = $this->createUrl($options, $mappingValues); 40 | $view->vars['content'] = $options['content']; 41 | $view->vars['attr'] = $options['attr']; 42 | $view->vars['url_attr'] = $options['url_attr']; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function configureOptions(OptionsResolver $resolver) 49 | { 50 | $resolver->setDefaults( 51 | [ 52 | 'redirect_uri' => null, 53 | 'content' => null, 54 | 'attr' => [], 55 | 'url_attr' => [], 56 | 'url' => null, 57 | 'uri_scheme' => null, 58 | 59 | // Remove requirement for label 60 | 'label' => function (Options $options) { 61 | return $options['content']; 62 | }, 63 | ] 64 | ); 65 | 66 | $resolver->setAllowedTypes('redirect_uri', ['string', 'null', 'callable']); 67 | $resolver->setAllowedTypes('url', ['string', 'null']); 68 | $resolver->setAllowedTypes('uri_scheme', ['string', 'callable', 'null']); 69 | $resolver->setAllowedTypes('content', ['null', 'string', 'callable']); 70 | $resolver->setAllowedTypes('attr', ['array']); 71 | $resolver->setAllowedTypes('url_attr', ['array']); 72 | } 73 | 74 | private static function wrapValues(array $values) 75 | { 76 | $return = []; 77 | 78 | foreach ($values as $key => $value) { 79 | $return['{'.$key.'}'] = $value; 80 | } 81 | 82 | return $return; 83 | } 84 | 85 | private function createUrl(array $options, $mappingValues): string 86 | { 87 | if (null !== $options['url']) { 88 | return $options['url']; 89 | } 90 | 91 | if (null === $options['uri_scheme']) { 92 | throw new InvalidConfigurationException('Action needs an "url" or "uri_scheme" but none is provided.'); 93 | } 94 | 95 | if (is_object($options['uri_scheme'])) { 96 | $url = $options['uri_scheme']($mappingValues); 97 | } else { 98 | $url = strtr($options['uri_scheme'], $this->wrapValues($mappingValues)); 99 | } 100 | 101 | return $url.$this->getRedirectUrl($options, $url, $mappingValues); 102 | } 103 | 104 | private function getRedirectUrl(array $options, $url, $mappingValues): string 105 | { 106 | if (null === $options['redirect_uri']) { 107 | return ''; 108 | } 109 | 110 | if (is_object($options['redirect_uri'])) { 111 | $options['redirect_uri'] = $options['redirect_uri']($mappingValues); 112 | } 113 | 114 | return (false !== strpos($url, '?') ? '&' : '?').'redirect_uri='.urlencode($options['redirect_uri']); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Extension/Core/Type/BaseType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\Type; 15 | 16 | use Rollerworks\Component\Datagrid\Column\AbstractType; 17 | use Rollerworks\Component\Datagrid\Column\CellView; 18 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 19 | use Rollerworks\Component\Datagrid\Column\HeaderView; 20 | use Symfony\Component\OptionsResolver\OptionsResolver; 21 | use Symfony\Component\PropertyAccess\PropertyPath; 22 | 23 | /** 24 | * Encapsulates common logic of {@link ColumnType} and {@link ComponendColumnType}. 25 | * 26 | * This type does not appear in the column's type inheritance chain and as such 27 | * cannot be extended (via {@link \Rollerworks\Component\Datagrid\Column\ColumnTypeExtensionInterface}) nor themed. 28 | * 29 | * @author Sebastiaan Stok 30 | */ 31 | abstract class BaseType extends AbstractType 32 | { 33 | public function buildHeaderView(HeaderView $view, ColumnInterface $column, array $options) 34 | { 35 | $blockName = (string) $options['block_name']; 36 | 37 | if ('' === $blockName) { 38 | $blockName = $view->datagrid->name.'_'; 39 | 40 | // Child-columns must be prefixed with there parents name to prevent collisions. 41 | if (isset($options['parent_column'])) { 42 | $blockName .= $options['parent_column']->getName().'_'; 43 | } 44 | 45 | $blockName .= $column->getName(); 46 | } 47 | 48 | $uniqueBlockPrefix = '_'.$blockName; 49 | $blockPrefixes = []; 50 | 51 | for ($type = $column->getType(); null !== $type; $type = $type->getParent()) { 52 | array_unshift($blockPrefixes, $type->getBlockPrefix()); 53 | } 54 | 55 | $blockPrefixes[] = $uniqueBlockPrefix; 56 | 57 | $view->vars = array_replace($view->vars, [ 58 | 'label_attr' => $options['label_attr'], 59 | 'header_attr' => $options['header_attr'], 60 | 'cell_attr' => $options['header_attr'], 61 | 'label_translation_domain' => $options['label_translation_domain'], 62 | 'unique_block_prefix' => $uniqueBlockPrefix, 63 | 'block_prefixes' => $blockPrefixes, 64 | // The cache key is used for caching in the render-engine. 65 | // multiple columns can share the same block-name but have a different type (sub columns). 66 | 'cache_key' => $uniqueBlockPrefix.'_'.$column->getType()->getBlockPrefix(), 67 | ]); 68 | } 69 | 70 | public function buildCellView(CellView $view, ColumnInterface $column, array $options) 71 | { 72 | $parent = $view->column; 73 | 74 | // Set shared information from the header. 75 | // This information is not recomputed for better performance. 76 | // Each header is created once, but this method will be called 77 | // 5000 times for a grid with 500 rows! 78 | 79 | $view->vars = array_replace($view->vars, [ 80 | 'cell_attr' => $options['cell_attr'], 81 | 'unique_block_prefix' => $parent->vars['unique_block_prefix'], 82 | 'block_prefixes' => $parent->vars['block_prefixes'], 83 | 'cache_key' => $parent->vars['cache_key'], 84 | ]); 85 | } 86 | 87 | public function configureOptions(OptionsResolver $resolver) 88 | { 89 | $resolver->setDefaults([ 90 | 'label' => null, 91 | 'label_attr' => [], 92 | 'header_attr' => [], 93 | 'cell_attr' => [], 94 | 'label_translation_domain' => null, 95 | 'block_name' => null, 96 | ]); 97 | 98 | $resolver->setDefault('parent_column', null); 99 | $resolver->setDefault('data_provider', null); 100 | 101 | $resolver->setAllowedTypes('label', ['string', 'null']); 102 | $resolver->setAllowedTypes('label_attr', 'array'); 103 | $resolver->setAllowedTypes('header_attr', 'array'); 104 | $resolver->setAllowedTypes('cell_attr', 'array'); 105 | 106 | $resolver->setAllowedTypes('data_provider', ['Closure', 'null', 'string', PropertyPath::class]); 107 | } 108 | 109 | public function getParent() 110 | { 111 | // no-op. 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Extension/Core/Type/BatchType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\Type; 15 | 16 | use Rollerworks\Component\Datagrid\Column\AbstractType; 17 | use Rollerworks\Component\Datagrid\Column\CellView; 18 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 19 | use Rollerworks\Component\Datagrid\Column\HeaderView; 20 | use Rollerworks\Component\Datagrid\Exception\UnexpectedTypeException; 21 | 22 | class BatchType extends AbstractType 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function buildCellView(CellView $view, ColumnInterface $column, array $options) 28 | { 29 | if (!is_scalar($view->value)) { 30 | throw new UnexpectedTypeException($view->value, 'scalar'); 31 | } 32 | 33 | $id = str_replace(':', '-', sprintf('%s-%s__%s', $view->datagrid->name, $view->name, $view->value)); 34 | 35 | // Strip leading underscores and digits. These are allowed in 36 | // form names, but not in HTML4 ID attributes. 37 | // http://www.w3.org/TR/html401/struct/global.html#adef-id 38 | $id = ltrim($id, '_0123456789'); 39 | 40 | $view->vars['datagrid_name'] = $view->datagrid->name; 41 | $view->vars['selection_name'] = sprintf('%s[%s][]', $view->datagrid->name, $view->name); 42 | $view->vars['selection_id'] = $id; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function buildHeaderView(HeaderView $view, ColumnInterface $column, array $options) 49 | { 50 | $view->vars['datagrid_name'] = $view->datagrid->name; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Extension/Core/Type/BooleanType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\Type; 15 | 16 | use Rollerworks\Component\Datagrid\Column\AbstractType; 17 | use Symfony\Component\OptionsResolver\Options; 18 | use Symfony\Component\OptionsResolver\OptionsResolver; 19 | 20 | /** 21 | * @author Sebastiaan Stok 22 | * @author FSi sp. z o.o. 23 | */ 24 | class BooleanType extends AbstractType 25 | { 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function getParent() 30 | { 31 | return TextType::class; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function configureOptions(OptionsResolver $resolver) 38 | { 39 | $resolver->setDefaults([ 40 | 'true_value' => 'true', 41 | 'false_value' => 'false', 42 | 'value_format' => function (Options $options) { 43 | $trueValue = $options['true_value']; 44 | $falseValue = $options['false_value']; 45 | 46 | // Return a closure for later execution (the actual formatter) 47 | return function ($value) use ($trueValue, $falseValue) { 48 | $value = (array) $value; 49 | 50 | $boolValue = true; 51 | foreach ($value as $val) { 52 | $boolValue = (bool) ($boolValue & (bool) $val); 53 | if (!$boolValue) { 54 | break; 55 | } 56 | } 57 | 58 | return $boolValue ? $trueValue : $falseValue; 59 | }; 60 | }, 61 | ]); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Extension/Core/Type/ColumnType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\Type; 15 | 16 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 17 | use Rollerworks\Component\Datagrid\Exception\DataProviderException; 18 | use Rollerworks\Component\Datagrid\Util\StringUtil; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException; 21 | use Symfony\Component\PropertyAccess\PropertyAccess; 22 | use Symfony\Component\PropertyAccess\PropertyAccessor; 23 | use Symfony\Component\PropertyAccess\PropertyPath; 24 | 25 | class ColumnType extends BaseType 26 | { 27 | /** 28 | * @var PropertyAccessor 29 | */ 30 | private $propertyAccessor; 31 | 32 | /** 33 | * ColumnType constructor. 34 | * 35 | * @param PropertyAccessor|null $propertyAccessor 36 | */ 37 | public function __construct(PropertyAccessor $propertyAccessor = null) 38 | { 39 | if (null === $propertyAccessor) { 40 | $propertyAccessor = PropertyAccess::createPropertyAccessor(); 41 | } 42 | 43 | $this->propertyAccessor = $propertyAccessor; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function configureOptions(OptionsResolver $resolver) 50 | { 51 | parent::configureOptions($resolver); 52 | 53 | $resolver->setDefault('parent_data_provider', null); 54 | $resolver->setAllowedTypes('parent_data_provider', ['Closure', 'null']); 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function buildColumn(ColumnInterface $column, array $options) 61 | { 62 | $dataProvider = $options['data_provider']; 63 | $parentDataProvider = $options['parent_data_provider']; 64 | 65 | if (!$dataProvider instanceof \Closure) { 66 | $dataProvider = $parentDataProvider ?: function ($data) use ($column, $dataProvider) { 67 | static $path; 68 | 69 | if (null === $path) { 70 | $path = $this->createDataProviderPath($column, $data, $dataProvider); 71 | } 72 | 73 | return $this->propertyAccessor->getValue($data, $path); 74 | }; 75 | } 76 | 77 | $column->setDataProvider($dataProvider); 78 | } 79 | 80 | /** 81 | * Returns the prefix of the template block name for this type. 82 | * 83 | * @return string The prefix of the template block name 84 | */ 85 | public function getBlockPrefix(): string 86 | { 87 | return StringUtil::fqcnToBlockPrefix(get_class($this)); 88 | } 89 | 90 | private function createDataProviderPath(ColumnInterface $column, $data, $customPath): PropertyPath 91 | { 92 | try { 93 | if (null === $customPath) { 94 | $name = $column->getName(); 95 | 96 | if (!$this->propertyAccessor->isReadable($data, $path = new PropertyPath(sprintf('[%s]', $name))) && 97 | !$this->propertyAccessor->isReadable($data, $path = new PropertyPath($name)) 98 | ) { 99 | throw DataProviderException::autoAccessorUnableToGetValue($name); 100 | } 101 | 102 | return $path; 103 | } 104 | 105 | if (!$this->propertyAccessor->isReadable($data, $path = new PropertyPath($customPath))) { 106 | throw DataProviderException::pathAccessorUnableToGetValue($column->getName(), $path); 107 | } 108 | 109 | return $path; 110 | } catch (InvalidPropertyPathException $e) { 111 | throw DataProviderException::invalidPropertyPath($column->getName(), $e); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Extension/Core/Type/CompoundColumnType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\Type; 15 | 16 | use Rollerworks\Component\Datagrid\Column\CellView; 17 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 18 | use Rollerworks\Component\Datagrid\Column\CompoundColumn; 19 | use Rollerworks\Component\Datagrid\Column\HeaderView; 20 | 21 | /** 22 | * CompoundColumn allows multiple sub-columns for advanced view building. 23 | * 24 | * @author Sebastiaan Stok 25 | */ 26 | class CompoundColumnType extends BaseType 27 | { 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function buildColumn(ColumnInterface $column, array $options) 32 | { 33 | // Simple pass all $data to the sub-columns. 34 | $column->setDataProvider( 35 | function ($data) { 36 | return $data; 37 | } 38 | ); 39 | } 40 | 41 | /** 42 | * @param HeaderView $view 43 | * @param ColumnInterface|CompoundColumn $column 44 | * @param array $options 45 | */ 46 | public function buildHeaderView(HeaderView $view, ColumnInterface $column, array $options) 47 | { 48 | parent::buildHeaderView($view, $column, $options); 49 | 50 | // The header information contains the actual block information (and cache key) 51 | $datagrid = $view->datagrid; 52 | 53 | $headers = []; 54 | 55 | foreach ($column->getColumns() as $subColumn) { 56 | $headers[$subColumn->getName()] = $subColumn->createHeaderView($datagrid); 57 | } 58 | 59 | $view->vars['_sub_headers'] = $headers; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function buildCellView(CellView $view, ColumnInterface $column, array $options) 66 | { 67 | $parent = $view->column; 68 | 69 | // Set shared information from the header. 70 | // This information is not recomputed for better performance. 71 | // Each header is created once, but this method will be called 72 | // 5000 times for a grid with 500 rows! 73 | 74 | $view->vars = array_replace($view->vars, [ 75 | 'cell_attr' => $options['cell_attr'], 76 | 'unique_block_prefix' => $parent->vars['unique_block_prefix'], 77 | 'block_prefixes' => $parent->vars['block_prefixes'], 78 | 'cache_key' => $parent->vars['cache_key'], 79 | ]); 80 | 81 | $cells = []; 82 | 83 | $headers = $view->column->vars['_sub_headers']; 84 | 85 | /** @var CompoundColumn $column */ 86 | foreach ($column->getColumns() as $subColumn) { 87 | $name = $subColumn->getName(); 88 | 89 | $subView = $subColumn->createCellView($headers[$name], $view->source, $view->vars['row']); 90 | $subView->vars['compound'] = true; 91 | 92 | $cells[$name] = $subView; 93 | } 94 | 95 | return $view->value = $cells; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Extension/Core/Type/DateTimeType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\Type; 15 | 16 | use Rollerworks\Component\Datagrid\Column\AbstractType; 17 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 18 | use Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\ChainTransformer; 19 | use Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer; 20 | use Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\StringToDateTimeTransformer; 21 | use Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\TimestampToDateTimeTransformer; 22 | use Symfony\Component\OptionsResolver\OptionsResolver; 23 | 24 | /** 25 | * DateTimeType. 26 | * 27 | * @author Sebastiaan Stok 28 | */ 29 | class DateTimeType extends AbstractType 30 | { 31 | /** 32 | * @var array 33 | */ 34 | private static $acceptedFormats = [ 35 | \IntlDateFormatter::FULL, 36 | \IntlDateFormatter::LONG, 37 | \IntlDateFormatter::MEDIUM, 38 | \IntlDateFormatter::SHORT, 39 | \IntlDateFormatter::NONE, 40 | ]; 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function buildColumn(ColumnInterface $column, array $options) 46 | { 47 | $transformer = new ChainTransformer(); 48 | 49 | if ('string' === $options['input']) { 50 | $transformer->append( 51 | new StringToDateTimeTransformer($options['model_timezone'], $options['model_timezone']) 52 | ); 53 | } elseif ('timestamp' === $options['input']) { 54 | $transformer->append( 55 | new TimestampToDateTimeTransformer($options['model_timezone'], $options['model_timezone']) 56 | ); 57 | } 58 | 59 | $transformer->append(new DateTimeToLocalizedStringTransformer( 60 | $options['model_timezone'], 61 | $options['view_timezone'], 62 | $options['date_format'], 63 | $options['time_format'], 64 | $options['calendar'], 65 | $options['format'] 66 | )); 67 | 68 | $column->setViewTransformer($transformer); 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function configureOptions(OptionsResolver $resolver) 75 | { 76 | $resolver->setDefaults([ 77 | 'input' => 'datetime', 78 | 'model_timezone' => null, 79 | 'view_timezone' => null, 80 | 'date_format' => \IntlDateFormatter::MEDIUM, 81 | 'time_format' => \IntlDateFormatter::MEDIUM, 82 | 'calendar' => \IntlDateFormatter::GREGORIAN, 83 | 'format' => null, 84 | ]); 85 | 86 | $resolver->setAllowedValues('date_format', self::$acceptedFormats); 87 | $resolver->setAllowedValues('time_format', self::$acceptedFormats); 88 | 89 | $resolver->setAllowedTypes('model_timezone', ['null', 'string']); 90 | $resolver->setAllowedTypes('view_timezone', ['null', 'string']); 91 | $resolver->setAllowedTypes('format', ['null', 'string']); 92 | $resolver->setAllowedTypes('calendar', ['int', 'IntlCalendar']); 93 | $resolver->setAllowedValues('input', ['string', 'timestamp', 'datetime']); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Extension/Core/Type/MoneyType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\Type; 15 | 16 | use Rollerworks\Component\Datagrid\Column\AbstractType; 17 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 18 | use Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\MoneyToLocalizedStringTransformer; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | 21 | /** 22 | * @author Sebastiaan Stok 23 | */ 24 | class MoneyType extends AbstractType 25 | { 26 | public function buildColumn(ColumnInterface $column, array $options) 27 | { 28 | $column->setViewTransformer(new MoneyToLocalizedStringTransformer( 29 | $options['precision'], 30 | $options['grouping'], 31 | $options['rounding_mode'], 32 | $options['divisor'], 33 | $options['currency'] 34 | )); 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function configureOptions(OptionsResolver $resolver) 41 | { 42 | $resolver->setDefaults([ 43 | 'precision' => 2, 44 | 'grouping' => false, 45 | 'divisor' => 1, 46 | 'currency' => 'EUR', 47 | 'rounding_mode' => \NumberFormatter::ROUND_HALFUP, 48 | ]); 49 | 50 | $resolver->setAllowedValues( 51 | 'rounding_mode', 52 | [ 53 | \NumberFormatter::ROUND_FLOOR, 54 | \NumberFormatter::ROUND_DOWN, 55 | \NumberFormatter::ROUND_HALFDOWN, 56 | \NumberFormatter::ROUND_HALFEVEN, 57 | \NumberFormatter::ROUND_HALFUP, 58 | \NumberFormatter::ROUND_UP, 59 | \NumberFormatter::ROUND_CEILING, 60 | ] 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Extension/Core/Type/NumberType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\Type; 15 | 16 | use Rollerworks\Component\Datagrid\Column\AbstractType; 17 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 18 | use Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | 21 | class NumberType extends AbstractType 22 | { 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function buildColumn(ColumnInterface $column, array $options) 27 | { 28 | $column->setViewTransformer(new NumberToLocalizedStringTransformer( 29 | $options['precision'], 30 | $options['grouping'], 31 | $options['rounding_mode'] 32 | )); 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function configureOptions(OptionsResolver $resolver) 39 | { 40 | $resolver->setDefaults([ 41 | // default precision is locale specific (usually around 3) 42 | 'precision' => null, 43 | 'grouping' => false, 44 | 'rounding_mode' => \NumberFormatter::ROUND_HALFUP, 45 | ]); 46 | 47 | $resolver->setAllowedValues( 48 | 'rounding_mode', 49 | [ 50 | \NumberFormatter::ROUND_FLOOR, 51 | \NumberFormatter::ROUND_DOWN, 52 | \NumberFormatter::ROUND_HALFDOWN, 53 | \NumberFormatter::ROUND_HALFEVEN, 54 | \NumberFormatter::ROUND_HALFUP, 55 | \NumberFormatter::ROUND_UP, 56 | \NumberFormatter::ROUND_CEILING, 57 | ] 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Extension/Core/Type/TextType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Extension\Core\Type; 15 | 16 | use Rollerworks\Component\Datagrid\Column\AbstractType; 17 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 18 | use Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\ChainTransformer; 19 | use Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\EmptyValueTransformer; 20 | use Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\ValueFormatTransformer; 21 | use Symfony\Component\OptionsResolver\OptionsResolver; 22 | 23 | class TextType extends AbstractType 24 | { 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function buildColumn(ColumnInterface $column, array $options) 29 | { 30 | $transformer = new ChainTransformer(); 31 | 32 | if (null !== $options['empty_value']) { 33 | $transformer->append(new EmptyValueTransformer($options['empty_value'])); 34 | } 35 | 36 | if (null !== $options['value_format'] || null !== $options['value_glue']) { 37 | $transformer->append(new ValueFormatTransformer($options['value_glue'], $options['value_format'])); 38 | } 39 | 40 | $column->setViewTransformer($transformer); 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function configureOptions(OptionsResolver $resolver) 47 | { 48 | $resolver->setDefaults([ 49 | 'value_glue' => null, 50 | 'value_format' => null, 51 | 'empty_value' => null, 52 | ]); 53 | 54 | $resolver->setAllowedTypes('value_glue', ['string', 'null']); 55 | $resolver->setAllowedTypes('value_format', ['string', 'callable', 'null']); 56 | $resolver->setAllowedTypes('empty_value', ['string', 'array', 'null']); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/PreloadedExtension.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid; 15 | 16 | use Rollerworks\Component\Datagrid\Column\ColumnTypeInterface; 17 | use Rollerworks\Component\Datagrid\Exception\InvalidArgumentException; 18 | 19 | class PreloadedExtension implements DatagridExtensionInterface 20 | { 21 | private $columnTypes = []; 22 | private $typeColumnExtensions = []; 23 | 24 | /** 25 | * Constructor. 26 | * 27 | * @param ColumnTypeInterface[] $types The column-types that the extension 28 | * should support 29 | * @param array[] $typeExtensions The column-type extensions that the extension 30 | * should support. 31 | * Registered as [type => [ColumnTypeExtensionInterface object, ...]] 32 | */ 33 | public function __construct(array $types, array $typeExtensions) 34 | { 35 | $this->columnTypes = $types; 36 | $this->typeColumnExtensions = $typeExtensions; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function getType(string $name): ColumnTypeInterface 43 | { 44 | if (!isset($this->columnTypes[$name])) { 45 | throw new InvalidArgumentException( 46 | sprintf('The column-type "%s" can not be loaded by this extension.', $name) 47 | ); 48 | } 49 | 50 | return $this->columnTypes[$name]; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function hasType(string $name): bool 57 | { 58 | return isset($this->columnTypes[$name]); 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function getTypeExtensions(string $type): array 65 | { 66 | return isset($this->typeColumnExtensions[$type]) ? $this->typeColumnExtensions[$type] : []; 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | public function hasTypeExtensions(string $type): bool 73 | { 74 | return !empty($this->typeColumnExtensions[$type]); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Test/ColumnTypeTestCase.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Test; 15 | 16 | use Rollerworks\Component\Datagrid\Datagrid; 17 | 18 | abstract class ColumnTypeTestCase extends DatagridIntegrationTestCase 19 | { 20 | public static function assertDateTimeEquals(\DateTime $expected, \DateTime $actual) 21 | { 22 | self::assertEquals($expected->format('c'), $actual->format('c')); 23 | } 24 | 25 | protected function assertCellValueEquals($expectedValue, $data, array $options = [], array $viewAttributes = null, $idx = 1) 26 | { 27 | $column = $this->factory->createColumn( 28 | 'id', 29 | $this->getTestedType(), 30 | array_merge( 31 | [ 32 | 'label' => 'My label', 33 | 'data_provider' => !is_array($data) ? function ($data) { 34 | return $data->key; 35 | } : null, 36 | ], 37 | $options 38 | ) 39 | ); 40 | 41 | $datagrid = new Datagrid('grid', [$column]); 42 | 43 | if (!is_array($data)) { 44 | $object = new \stdClass(); 45 | $object->key = $data; 46 | $data = [$idx => $object]; 47 | } 48 | 49 | $datagrid->setData($data); 50 | $datagridView = $datagrid->createView(); 51 | 52 | $view = $column->createCellView($datagridView->columns['id'], $data[$idx], $idx); 53 | 54 | $this->assertEquals($expectedValue, $view->value); 55 | 56 | if (null !== $viewAttributes) { 57 | $viewAttributes['row'] = 1; 58 | 59 | self::assertViewVarsEquals($viewAttributes, $view); 60 | } 61 | } 62 | 63 | protected function assertCellValueNotEquals($expectedValue, $data, array $options = [], $idx = 1) 64 | { 65 | $column = $this->factory->createColumn( 66 | 'id', 67 | $this->getTestedType(), 68 | array_merge( 69 | [ 70 | 'label' => 'My label', 71 | 'data_provider' => !is_array($data) ? function ($data) { 72 | return $data->key; 73 | } : null, 74 | ], 75 | $options 76 | ) 77 | ); 78 | 79 | $datagrid = new Datagrid('grid', [$column]); 80 | 81 | if (!is_array($data)) { 82 | $object = new \stdClass(); 83 | $object->key = $data; 84 | 85 | $data = [$idx => $object]; 86 | } 87 | 88 | $datagrid->setData($data); 89 | $datagridView = $datagrid->createView(); 90 | 91 | $view = $column->createCellView($datagridView->columns['id'], $data[$idx], $idx); 92 | $this->assertNotEquals($expectedValue, $view->value); 93 | } 94 | 95 | protected static function assertViewVarsEquals(array $viewAttributes, $view) 96 | { 97 | self::assertEquals( 98 | self::normalizeViewExpectation($viewAttributes, $view), 99 | $view->vars 100 | ); 101 | } 102 | 103 | abstract protected function getTestedType(): string; 104 | 105 | /** 106 | * Ensures the 'base' view-vars are set. 107 | * 108 | * @param array $viewAttributes 109 | * @param $view 110 | * 111 | * @return array 112 | */ 113 | protected static function normalizeViewExpectation(array $viewAttributes, $view) 114 | { 115 | if (!isset($view->vars['unique_block_prefix'])) { 116 | return $viewAttributes; 117 | } 118 | 119 | $viewAttributes = array_replace( 120 | [ 121 | 'unique_block_prefix' => $view->vars['unique_block_prefix'], 122 | 'block_prefixes' => $view->vars['block_prefixes'], 123 | 'cache_key' => $view->vars['cache_key'], 124 | ], 125 | $viewAttributes 126 | ); 127 | 128 | if (isset($view->vars['header_attr']) && !isset($viewAttributes['header_attr'])) { 129 | $viewAttributes['header_attr'] = $view->vars['header_attr']; 130 | } 131 | 132 | if (isset($view->vars['label_attr']) && !isset($viewAttributes['label_attr'])) { 133 | $viewAttributes['label_attr'] = $view->vars['label_attr']; 134 | } 135 | 136 | if (isset($view->vars['cell_attr']) && !isset($viewAttributes['cell_attr'])) { 137 | $viewAttributes['cell_attr'] = $view->vars['cell_attr']; 138 | } 139 | 140 | return $viewAttributes; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Test/DatagridIntegrationTestCase.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Test; 15 | 16 | use PHPUnit\Framework\TestCase; 17 | use Rollerworks\Component\Datagrid\Column\ColumnTypeRegistry; 18 | use Rollerworks\Component\Datagrid\Column\ResolvedColumnTypeFactory; 19 | use Rollerworks\Component\Datagrid\DatagridFactory; 20 | use Rollerworks\Component\Datagrid\DatagridRegistry; 21 | use Rollerworks\Component\Datagrid\Extension\Core\CoreExtension; 22 | 23 | abstract class DatagridIntegrationTestCase extends TestCase 24 | { 25 | /** 26 | * @var DatagridFactory 27 | */ 28 | protected $factory; 29 | 30 | protected function setUp() 31 | { 32 | $resolvedTypeFactory = new ResolvedColumnTypeFactory(); 33 | 34 | $extensions = [new CoreExtension()]; 35 | $extensions = array_merge($extensions, $this->getExtensions()); 36 | 37 | $typesRegistry = new ColumnTypeRegistry($extensions, $resolvedTypeFactory); 38 | $datagridRegistry = new DatagridRegistry(); 39 | $this->factory = new DatagridFactory($typesRegistry, $datagridRegistry); 40 | } 41 | 42 | protected function getExtensions(): array 43 | { 44 | return []; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Test/DatagridPerformanceTestCase.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Test; 15 | 16 | /** 17 | * Base class for performance tests. 18 | * 19 | * Copied from Doctrine 2's OrmPerformanceTestCase. 20 | * 21 | * @author Sebastiaan Stok 22 | * @author Bernhard Schussek 23 | */ 24 | abstract class DatagridPerformanceTestCase extends DatagridIntegrationTestCase 25 | { 26 | /** 27 | * @var int 28 | */ 29 | protected $maxRunningTime = 0; 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | protected function runTest() 35 | { 36 | $s = microtime(true); 37 | parent::runTest(); 38 | $time = microtime(true) - $s; 39 | 40 | if ($this->maxRunningTime !== 0 && $time > $this->maxRunningTime) { 41 | $this->fail( 42 | sprintf( 43 | 'expected running time: <= %s but was: %s', 44 | 45 | $this->maxRunningTime, 46 | $time 47 | ) 48 | ); 49 | } 50 | } 51 | 52 | /** 53 | * @param int $maxRunningTime 54 | * 55 | * @throws \InvalidArgumentException 56 | */ 57 | public function setMaxRunningTime(int $maxRunningTime) 58 | { 59 | if ($maxRunningTime >= 0) { 60 | $this->maxRunningTime = $maxRunningTime; 61 | } else { 62 | throw new \InvalidArgumentException(); 63 | } 64 | } 65 | 66 | /** 67 | * @return int 68 | */ 69 | public function getMaxRunningTime(): int 70 | { 71 | return $this->maxRunningTime; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Test/MockTestCase.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Test; 15 | 16 | use PHPUnit\Framework\TestCase; 17 | use Rollerworks\Component\Datagrid\Column\CellView; 18 | use Rollerworks\Component\Datagrid\Column\ColumnInterface; 19 | use Rollerworks\Component\Datagrid\Column\HeaderView; 20 | use Rollerworks\Component\Datagrid\Column\ResolvedColumnTypeInterface; 21 | use Rollerworks\Component\Datagrid\DatagridInterface; 22 | use Rollerworks\Component\Datagrid\DatagridView; 23 | use Rollerworks\Component\Datagrid\Extension\Core\Type\TextType; 24 | use Rollerworks\Component\Datagrid\Util\StringUtil; 25 | 26 | abstract class MockTestCase extends TestCase 27 | { 28 | protected function createColumn(string $name = 'foo', string $type = TextType::class): ColumnInterface 29 | { 30 | $resolvedType = $this->createMock(ResolvedColumnTypeInterface::class); 31 | $resolvedType->expects(self::any()) 32 | ->method('getInnerType') 33 | ->willReturn(new $type()); 34 | 35 | $resolvedType->expects(self::any()) 36 | ->method('getBlockPrefix') 37 | ->willReturn(StringUtil::fqcnToBlockPrefix($type)); 38 | 39 | $column = $this->createMock(ColumnInterface::class); 40 | $column->expects(self::any()) 41 | ->method('getName') 42 | ->willReturn($name); 43 | 44 | $column->expects(self::any()) 45 | ->method('getType') 46 | ->willReturn($resolvedType); 47 | 48 | $column->expects(self::any()) 49 | ->method('createHeaderView') 50 | ->withAnyParameters() 51 | ->willReturnCallback( 52 | function (DatagridView $datagrid) use ($column, $name) { 53 | return new HeaderView($column, $datagrid, $name); 54 | } 55 | ); 56 | 57 | $column->expects(self::any()) 58 | ->method('createCellView') 59 | ->withAnyParameters() 60 | ->willReturnCallback( 61 | function (HeaderView $header, $source, $index) { 62 | $view = new CellView($header, $header->datagrid); 63 | $view->vars['row'] = $index; 64 | $view->value = $source; 65 | $view->source = $source; 66 | 67 | return $view; 68 | } 69 | ); 70 | 71 | return $column; 72 | } 73 | 74 | /** 75 | * @param array $columns 76 | * @param array $source 77 | * @param string $name 78 | * 79 | * @return DatagridInterface 80 | */ 81 | protected function createDatagrid(array $columns, $source, string $name = 'users'): DatagridInterface 82 | { 83 | $datagrid = $this->createMock(DatagridInterface::class); 84 | $datagrid->expects(self::any()) 85 | ->method('getName') 86 | ->willReturn($name); 87 | 88 | $datagrid->expects(self::any()) 89 | ->method('getColumns') 90 | ->willReturn($columns); 91 | 92 | $datagrid->expects(self::any()) 93 | ->method('getData') 94 | ->willReturn($source); 95 | 96 | return $datagrid; 97 | } 98 | 99 | protected function createDatagridViewNoInit(DatagridInterface $datagrid, array $vars = []): DatagridView 100 | { 101 | $view = new DatagridView($datagrid); 102 | $view->vars = $vars; 103 | 104 | return $view; 105 | } 106 | 107 | protected function createDatagridView(DatagridInterface $datagrid, array $vars = []): DatagridView 108 | { 109 | $view = new DatagridView($datagrid); 110 | $view->vars = $vars; 111 | 112 | $view->init($datagrid); 113 | 114 | return $view; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Util/CompoundColumnBuilder.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Util; 15 | 16 | use Rollerworks\Component\Datagrid\Column\CompoundColumn; 17 | use Rollerworks\Component\Datagrid\DatagridBuilderInterface; 18 | use Rollerworks\Component\Datagrid\DatagridFactoryInterface; 19 | use Rollerworks\Component\Datagrid\Extension\Core\Type\CompoundColumnType; 20 | 21 | /** 22 | * The CompoundColumnBuilder creates a new CompoundColumn. 23 | * 24 | * A CompoundColumn allows to group multiple columns together, 25 | * eg. one more more date value or one or more row actions. 26 | * 27 | * The 'data_provider' of the CompoundColumn will be set as the 'data_provider' 28 | * for each sub-column, unless a sub column sets the 'data_provider' explicitly. 29 | * 30 | * 31 | * createCompound('actions', ['label' => 'Actions', 'data_provider' => function ($data) {return ['id' => $data->id();}]) 32 | * ->add('edit', ActionType::class, ['url_schema' => '/users/{id}/edit']) 33 | * ->add('delete', ActionType::class, ['url_schema' => '/users/{id}/edit']) 34 | * ->end() // This registers the CompoundColumn at the DatagridBuilder, and return the DatagridBuilder. 35 | * 36 | */ 37 | final class CompoundColumnBuilder implements CompoundColumnBuilderInterface 38 | { 39 | private $unresolvedColumns = []; 40 | private $factory; 41 | private $builder; 42 | private $name; 43 | private $options; 44 | private $type; 45 | 46 | public function __construct( 47 | DatagridFactoryInterface $factory, 48 | DatagridBuilderInterface $builder, 49 | string $name, 50 | array $options = [], 51 | string $type = null 52 | ) { 53 | if (!isset($options['data_provider'])) { 54 | $options['data_provider'] = null; 55 | } 56 | 57 | $this->factory = $factory; 58 | $this->builder = $builder; 59 | $this->name = $name; 60 | $this->options = $options; 61 | $this->type = $type ?? CompoundColumnType::class; 62 | } 63 | 64 | /** 65 | * Add a column to the builder. 66 | * 67 | * @param string $name 68 | * @param string $type 69 | * @param array $options 70 | * 71 | * @return self 72 | */ 73 | public function add(string $name, string $type = null, array $options = []): CompoundColumnBuilderInterface 74 | { 75 | $this->unresolvedColumns[$name] = [ 76 | 'type' => $type, 77 | 'options' => $options, 78 | ]; 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | */ 86 | public function remove(string $name): CompoundColumnBuilderInterface 87 | { 88 | unset($this->unresolvedColumns[$name]); 89 | 90 | return $this; 91 | } 92 | 93 | /** 94 | * {@inheritdoc} 95 | */ 96 | public function has(string $name): bool 97 | { 98 | return isset($this->unresolvedColumns[$name]); 99 | } 100 | 101 | /** 102 | * {@inheritdoc} 103 | */ 104 | public function end(): DatagridBuilderInterface 105 | { 106 | /** @var CompoundColumn $rootColumn */ 107 | $rootColumn = $this->factory->createColumn($this->name, $this->type, $this->options); 108 | 109 | $columns = []; 110 | 111 | foreach ($this->unresolvedColumns as $n => $column) { 112 | if (!isset($column['options']['data_provider'])) { 113 | $column['options']['data_provider'] = $this->options['data_provider']; 114 | } 115 | 116 | $columns[$n] = $this->factory->createColumn( 117 | $n, 118 | $column['type'], 119 | array_replace($column['options'], ['parent_column' => $rootColumn]) 120 | ); 121 | } 122 | 123 | $rootColumn->setColumns($columns); 124 | $this->builder->set($rootColumn); 125 | 126 | return $this->builder; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Util/CompoundColumnBuilderInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Util; 15 | 16 | use Rollerworks\Component\Datagrid\DatagridBuilderInterface; 17 | 18 | interface CompoundColumnBuilderInterface 19 | { 20 | /** 21 | * Add a column to the builder. 22 | * 23 | * @param string $name 24 | * @param string $type 25 | * @param array $options 26 | * 27 | * @return self 28 | */ 29 | public function add(string $name, string $type = null, array $options = []): CompoundColumnBuilderInterface; 30 | 31 | /** 32 | * Remove a column from the builder. 33 | * 34 | * @return self 35 | */ 36 | public function remove(string $name): CompoundColumnBuilderInterface; 37 | 38 | /** 39 | * Returns whether the builder has a column with the name. 40 | * 41 | * @return bool 42 | */ 43 | public function has(string $name): bool; 44 | 45 | /** 46 | * Ends the CompoundColumnBuilder process and registers the CompoundColumn 47 | * at the DatagridBuilder instance. 48 | * 49 | * @return DatagridBuilderInterface 50 | */ 51 | public function end(): DatagridBuilderInterface; 52 | } 53 | -------------------------------------------------------------------------------- /src/Util/DatagridFactoryBuilder.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Util; 15 | 16 | use Rollerworks\Component\Datagrid\Column\ColumnTypeExtensionInterface; 17 | use Rollerworks\Component\Datagrid\Column\ColumnTypeInterface; 18 | use Rollerworks\Component\Datagrid\Column\ColumnTypeRegistry; 19 | use Rollerworks\Component\Datagrid\Column\ResolvedColumnTypeFactory; 20 | use Rollerworks\Component\Datagrid\Column\ResolvedColumnTypeFactoryInterface; 21 | use Rollerworks\Component\Datagrid\DatagridExtensionInterface; 22 | use Rollerworks\Component\Datagrid\DatagridFactory; 23 | use Rollerworks\Component\Datagrid\DatagridRegistry; 24 | use Rollerworks\Component\Datagrid\DatagridRegistryInterface; 25 | use Rollerworks\Component\Datagrid\PreloadedExtension; 26 | 27 | /** 28 | * @author Sebastiaan Stok 29 | */ 30 | final class DatagridFactoryBuilder 31 | { 32 | private $resolvedTypeFactory; 33 | private $datagridRegistry; 34 | private $extensions = []; 35 | private $types = []; 36 | private $typeExtensions = []; 37 | 38 | /** 39 | * @param ResolvedColumnTypeFactoryInterface $resolvedTypeFactory 40 | * 41 | * @return DatagridFactoryBuilder 42 | */ 43 | public function setResolvedTypeFactory(ResolvedColumnTypeFactoryInterface $resolvedTypeFactory): self 44 | { 45 | $this->resolvedTypeFactory = $resolvedTypeFactory; 46 | 47 | return $this; 48 | } 49 | 50 | /** 51 | * @param DatagridRegistryInterface $datagridRegistry 52 | * 53 | * @return DatagridFactoryBuilder 54 | */ 55 | public function setDatagridRegistry(DatagridRegistryInterface $datagridRegistry): self 56 | { 57 | $this->datagridRegistry = $datagridRegistry; 58 | 59 | return $this; 60 | } 61 | 62 | /** 63 | * @param DatagridExtensionInterface $extension 64 | * 65 | * @return DatagridFactoryBuilder 66 | */ 67 | public function addExtension(DatagridExtensionInterface $extension): self 68 | { 69 | $this->extensions[] = $extension; 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * @param DatagridExtensionInterface[] $extensions 76 | * 77 | * @return DatagridFactoryBuilder 78 | */ 79 | public function addExtensions($extensions): self 80 | { 81 | $this->extensions = array_merge($this->extensions, $extensions); 82 | 83 | return $this; 84 | } 85 | 86 | /** 87 | * @param ColumnTypeInterface $type 88 | * 89 | * @return DatagridFactoryBuilder 90 | */ 91 | public function addType(ColumnTypeInterface $type): self 92 | { 93 | $this->types[get_class($type)] = $type; 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * @param ColumnTypeInterface[] $types 100 | * 101 | * @return DatagridFactoryBuilder 102 | */ 103 | public function addTypes(array $types): self 104 | { 105 | foreach ($types as $type) { 106 | $this->types[get_class($type)] = $type; 107 | } 108 | 109 | return $this; 110 | } 111 | 112 | /** 113 | * @param ColumnTypeExtensionInterface $typeExtension 114 | * 115 | * @return DatagridFactoryBuilder 116 | */ 117 | public function addTypeExtension(ColumnTypeExtensionInterface $typeExtension): self 118 | { 119 | $this->typeExtensions[$typeExtension->getExtendedType()][] = $typeExtension; 120 | 121 | return $this; 122 | } 123 | 124 | /** 125 | * @param ColumnTypeExtensionInterface[] $typeExtensions 126 | * 127 | * @return DatagridFactoryBuilder 128 | */ 129 | public function addTypeExtensions(array $typeExtensions): self 130 | { 131 | foreach ($typeExtensions as $typeExtension) { 132 | $this->typeExtensions[$typeExtension->getExtendedType()][] = $typeExtension; 133 | } 134 | 135 | return $this; 136 | } 137 | 138 | /** 139 | * @return DatagridFactory 140 | */ 141 | public function getDatagridFactory(): DatagridFactory 142 | { 143 | $extensions = $this->extensions; 144 | 145 | if (count($this->types) > 0 || count($this->typeExtensions) > 0) { 146 | $extensions[] = new PreloadedExtension($this->types, $this->typeExtensions); 147 | } 148 | 149 | $typesRegistry = new ColumnTypeRegistry( 150 | $extensions, 151 | $this->resolvedTypeFactory ?: new ResolvedColumnTypeFactory() 152 | ); 153 | 154 | return new DatagridFactory($typesRegistry, $this->datagridRegistry ?: new DatagridRegistry()); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Util/StringUtil.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Rollerworks\Component\Datagrid\Util; 15 | 16 | /** 17 | * @author Issei Murasawa 18 | * @author Bernhard Schussek 19 | */ 20 | final class StringUtil 21 | { 22 | /** 23 | * This class should not be instantiated. 24 | */ 25 | private function __construct() 26 | { 27 | } 28 | 29 | /** 30 | * Returns the trimmed data. 31 | * 32 | * @param string $string 33 | * 34 | * @return string 35 | */ 36 | public static function trim(string $string): string 37 | { 38 | if (null !== $result = @preg_replace('/^[\pZ\p{Cc}]+|[\pZ\p{Cc}]+$/u', '', $string)) { 39 | return $result; 40 | } 41 | 42 | return trim($string); 43 | } 44 | 45 | /** 46 | * Converts a fully-qualified class name to a block prefix. 47 | * 48 | * @param string $fqcn The fully-qualified class name 49 | * 50 | * @return string|null The block prefix or null if not a valid FQCN 51 | */ 52 | public static function fqcnToBlockPrefix(string $fqcn): string 53 | { 54 | // Non-greedy ("+?") to match "type" suffix, if present 55 | if (preg_match('~([^\\\\]+?)(type)?$~i', $fqcn, $matches)) { 56 | return strtolower( 57 | preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $matches[1]) 58 | ); 59 | } 60 | } 61 | } 62 | --------------------------------------------------------------------------------