Skip to content

Commit

Permalink
Merge pull request #303 from FriendsOfCake/bake-task-ci
Browse files Browse the repository at this point in the history
Bake task for filter collections
  • Loading branch information
ADmad authored Nov 9, 2020
2 parents 8376995 + eeb80e4 commit ee0b26f
Show file tree
Hide file tree
Showing 18 changed files with 504 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ jobs:
run: composer install

- name: Run phpcs
run: vendor/bin/phpcs -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/
run: vendor/bin/phpcs src/ tests/

- name: Run psalm
run: psalm --output-format=github
Expand Down
3 changes: 3 additions & 0 deletions .stickler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ linters:
extensions: 'php'
fixer: true

files:
ignore: ['tests/comparisons/*']

fixers:
enable: true
workflow: commit
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"cakephp/cakephp": "^4.0"
},
"require-dev": {
"cakephp/twig-view": "^1.1.1",
"cakephp/bake": "^2.2.0",
"phpunit/phpunit": "~8.5.0",
"cakephp/cakephp-codesniffer": "^4.0",
"muffin/webservice": "^3.0@beta"
Expand All @@ -45,8 +47,8 @@
"issues": "https://github.com/FriendsOfCake/search/issues"
},
"scripts": {
"cs-check": "phpcs -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/",
"cs-fix": "phpcbf --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/",
"cs-check": "phpcs -p src/ tests/",
"cs-fix": "phpcbf src/ tests/",
"stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:^0.12 psalm/phar:^3.8 && mv composer.backup composer.json",
"phpstan": "phpstan.phar analyse",
"psalm": "psalm.phar",
Expand Down
4 changes: 4 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,10 @@ When the FormProtection component is activated for the whole controller, it shou
$this->FormProtection->setConfig('unlockedActions', ['index']);
```

## Bake Filters

With the `filter_collection` bake task, you can generate filter collection classes easily.

## Tips

### IDE compatibility
Expand Down
8 changes: 8 additions & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<ruleset name="friendsofcake-search">
<config name="installed_paths" value="vendor/cakephp/cakephp-codesniffer/CakePHP"/>

<rule ref="CakePHP"/>

<exclude-pattern>*/tests/comparisons/*</exclude-pattern>
</ruleset>
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ parameters:
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false
paths:
- src
- src/
bootstrapFiles:
- tests/bootstrap.php
ignoreErrors:
Expand Down
249 changes: 249 additions & 0 deletions src/Command/BakeFilterCollectionCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
<?php
declare(strict_types=1);

namespace Search\Command;

use Bake\Command\BakeCommand;
use Bake\Utility\TemplateRenderer;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
use Cake\Core\Configure;
use Cake\Database\Exception;
use Cake\ORM\Table;

/**
* For generating filter collection classes.
*
* E.g.:
* src/Model/Filter/MyCustomFilterCollection.php
* src/Model/Filter/Admin/UsersFilterCollection.php
*/
class BakeFilterCollectionCommand extends BakeCommand
{
/**
* Task name used in path generation.
*
* @var string
*/
public $pathFragment = 'Model/Filter/';

/**
* @var string
*/
protected $_name;

/**
* @var string[][]
*/
protected $_map = [
'value' => [
'integer',
'tinyinteger',
'biginteger',
'smallinteger',
'uuid',
'binaryuuid',
'boolean',
],
'like' => [
'string',
'char',
],
];

/**
* @var string[]
*/
protected $_ignoreFields = [
'lft',
'rght',
'password',
];

/**
* @inheritDoc
*/
public static function defaultName(): string
{
return 'bake filter_collection';
}

/**
* Execute the command.
*
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return int|null The exit code or null for success
*/
public function execute(Arguments $args, ConsoleIo $io): ?int
{
$this->extractCommonProperties($args);

$this->bake((string)$args->getArgumentAt(0), $args, $io);

return static::CODE_SUCCESS;
}

/**
* @inheritDoc
*/
public function bake(string $name, Arguments $args, ConsoleIo $io): void
{
$this->_name = $name;

$fields = $this->getFields($name, $args->getArgumentAt(1));
$templateData = $this->templateData($args) + [
'fields' => $fields,
];

$renderer = new TemplateRenderer((string)$args->getOption('theme'));
$renderer->set('name', $name);
$renderer->set($templateData);
$contents = $renderer->generate($this->template());

$filename = $this->getPath($args) . $this->fileName($name);
$io->createFile($filename, $contents, (bool)$args->getOption('force'));
}

/**
* @inheritDoc
*/
public function template(): string
{
return 'Search.FilterCollection/filter_collection';
}

/**
* @param \Cake\Console\Arguments $arguments Arguments
* @return array
*/
public function templateData(Arguments $arguments): array
{
$name = $this->_name;
$namespace = Configure::read('App.namespace');
$pluginPath = '';
if ($this->plugin) {
$namespace = $this->_pluginNamespace($this->plugin);
$pluginPath = $this->plugin . '.';
}

$namespace .= '\\Model\\Filter';

$namespacePart = null;
if (strpos($name, '/') !== false) {
$parts = explode('/', $name);
$name = array_pop($parts);
$namespacePart = implode('\\', $parts);
}
if ($namespacePart) {
$namespace .= '\\' . $namespacePart;
}

$prefix = $arguments->getOption('prefix');
if ($prefix) {
$namespace .= '\\' . $prefix;
}

return [
'plugin' => $this->plugin,
'pluginPath' => $pluginPath,
'namespace' => $namespace,
'prefix' => $prefix,
'name' => $name,
];
}

/**
* @inheritDoc
*/
public function name(): string
{
return 'filter_collection';
}

/**
* @inheritDoc
*/
public function fileName(string $name): string
{
return $name . 'FilterCollection.php';
}

/**
* @param string $name Name of filter collection
* @param string|null $modelName Model name
* @return array
*/
protected function getFields(string $name, ?string $modelName): array
{
$model = $modelName ?: $name;
try {
$table = $this->getTableLocator()->get($model);
$columns = $table->getSchema()->columns();
} catch (Exception $exception) {
return [];
}

$fields = [];
foreach ($columns as $column) {
if ($this->shouldSkip($column, $table)) {
continue;
}

$columnInfo = (array)$table->getSchema()->getColumn($column);
$type = $columnInfo['type'];
if (in_array($type, $this->_map['value'], true)) {
$fields[$column] = 'value';
continue;
}

if (in_array($type, $this->_map['like'], true)) {
$fields[$column] = 'like';
continue;
}
}

return $fields;
}

/**
* Gets the option parser instance and configures it.
*
* @param \Cake\Console\ConsoleOptionParser $parser The option parser to update.
* @return \Cake\Console\ConsoleOptionParser
*/
public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
{
$parser = $this->_setCommonOptions($parser);

$parser->setDescription(
'Bake filter collections for a model search functionality.'
)->addArgument('name', [
'help' => 'Name of the filter collection to bake. You can use Plugin.name '
. 'as a shortcut for plugin baking. By default this will also try to find the model based on that name.',
'required' => true,
])->addArgument('model', [
'help' => 'Model to use if you use a custom collection name that does not match the model.',
])->addOption('prefix', [
'help' => 'The namespace prefix to use.',
'default' => false,
]);

return $parser;
}

/**
* Checks if this column should be skipped.
*
* This hook method can be extended and customized as per application needs.
*
* @param string $column Column name
* @param \Cake\ORM\Table $table Table instance
* @return bool
*/
protected function shouldSkip(string $column, Table $table): bool
{
return $column === $table->getPrimaryKey() || in_array($column, $this->_ignoreFields, true);
}
}
3 changes: 2 additions & 1 deletion src/Model/Filter/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ public function __construct(string $name, Manager $manager, array $config = [])

if (
!is_string($config['name']) ||
(empty($config['name']) && $config['name'] !== '0')
(empty($config['name']) &&
$config['name'] !== '0')
) {
throw new \InvalidArgumentException(
'The `$name` argument is invalid. Expected a non-empty string.'
Expand Down
7 changes: 0 additions & 7 deletions src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,4 @@ class Plugin extends BasePlugin
* @var bool
*/
protected $routesEnabled = false;

/**
* Console middleware
*
* @var bool
*/
protected $consoleEnabled = false;
}
3 changes: 2 additions & 1 deletion src/Processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ protected function _flattenParams(array $params, FilterCollectionInterface $filt
foreach ($params as $key => $value) {
if (
!is_array($value) ||
(!empty($filters[$key]) && $filters[$key]->getConfig('flatten') === false)
(!empty($filters[$key]) &&
$filters[$key]->getConfig('flatten') === false)
) {
$flattened[$key] = $value;
continue;
Expand Down
19 changes: 19 additions & 0 deletions templates/bake/FilterCollection/filter_collection.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace {{ namespace }};
use Search\Model\Filter\FilterCollection;
class {{ name }}FilterCollection extends FilterCollection
{
/**
* @return void
*/
public function initialize(): void
{
{% for field, type in fields %}
$this->{{ type }}('{{ field }}');
{% endfor %}
}
}
25 changes: 25 additions & 0 deletions tests/TestApp/Application.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);

namespace Search\Test\TestApp;

use Cake\Http\BaseApplication;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\RouteBuilder;

class Application extends BaseApplication
{
public function middleware(MiddlewareQueue $middleware): MiddlewareQueue
{
return $middleware;
}

public function routes(RouteBuilder $routes): void
{
}

public function bootstrap(): void
{
$this->addPlugin('Bake', ['boostrap' => true]);
}
}
Loading

0 comments on commit ee0b26f

Please sign in to comment.