Skip to content

Commit

Permalink
Merge pull request #2 from EdouardTack/shell
Browse files Browse the repository at this point in the history
Add command shell to create Action File
  • Loading branch information
HavokInspiration authored Aug 28, 2017
2 parents bd3cbc4 + 6a4301b commit c9aac48
Show file tree
Hide file tree
Showing 15 changed files with 752 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ before_script:

script:
- sh -c "if [ '$DEFAULT' = '1' ]; then vendor/bin/phpunit --stderr; fi"
- sh -c "if [ '$PHPSTAN' = '1' ]; then composer require --dev phpstan/phpstan:^0.7 && vendor/bin/phpstan analyse -l 5 src; fi"
- sh -c "if [ '$PHPSTAN' = '1' ]; then composer require --dev phpstan/phpstan:^0.8 && vendor/bin/phpstan analyse -c phpstan.neon -l 5 src; fi"
- sh -c "if [ '$CODECOVERAGE' = '1' ]; then vendor/bin/phpunit --coverage-clover=clover.xml || true; fi"

after_success:
Expand Down
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,46 @@ src/

Your `Action` classes are only expected to hold an `execute()` method. It can receive passed parameters as in regular controller actions (meaning the URL `/posts/edit/5` will pass `5` to the argument `$id` in the `execute()` method of the `EditAction` class in our previous example).

#### Using the `bake` command-line to create Action classes

You first need to load the plugin in your **config/bootstrap_cli.php** :

```php
Plugin::load('HavokInspiration/ActionsClass');
```

You can then create an Action class file with the following command :

```
bin/cake bake action Posts/Index
```

The command expects the name to get the controller name and the action name separated by a forward slash. For instance, the above example would create a `IndexAction` file for the `Posts` controller.

You can also specify the routing prefix your controller action lives under by using the `--prefix` option :

```
bin/cake bake action Posts/Index --prefix Admin
```

If you want to create an action file for a plugin, you can use the `--plugin` option :

```
bin/cake bake action Posts/Index --plugin MyPlugin
```

You can of course use both together :

```
bin/cake bake action Posts/Index --plugin MyPlugin --prefix Admin
```

By default, baking an action class will generate the corresponding test file. You can skip the test file generation by using the `--no-test` boolean flag :

```
bin/cake bake action Posts/Index --no-test
```

## Compatibility

This plugin was designed to have a maximum compatibility with the regular CakePHP behavior.
Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"cakephp/cakephp": "^3.4"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
"phpunit/phpunit": "^6.0",
"cakephp/bake": "@stable"
},
"autoload": {
"psr-4": {
Expand All @@ -24,6 +25,7 @@
"autoload-dev": {
"psr-4": {
"HavokInspiration\\ActionsClass\\Test\\": "tests",
"HavokInspiration\\ActionsClass\\PHPStan\\": "tests/PHPStan",
"Cake\\Test\\": "./vendor/cakephp/cakephp/tests",
"TestApp\\": "tests/test_app/TestApp",
"TestPlugin\\": "tests/test_app/Plugin/TestPlugin/src",
Expand Down
5 changes: 5 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
services:
-
class: HavokInspiration\ActionsClass\PHPStan\ShellPropertiesClassReflectionExtension
tags:
- phpstan.broker.propertiesClassReflectionExtension
211 changes: 211 additions & 0 deletions src/Shell/Task/ActionTask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
<?php
/**
* Copyright (c) Yves Piquel (http://www.havokinspiration.fr)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Yves Piquel (http://www.havokinspiration.fr)
* @link http://github.com/HavokInspiration/cakephp-actions-class
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
declare(strict_types=1);
namespace HavokInspiration\ActionsClass\Shell\Task;

use Cake\Console\Shell;
use Bake\Shell\Task\SimpleBakeTask;
use Cake\Core\Configure;

/**
* Command line to create HavokInspiration\ActionsClass action file
*/
class ActionTask extends SimpleBakeTask
{
/**
* {@inheritDoc}
*/
public $pathFragment = 'Controller/';

/**
* Tasks to be loaded by this Task
*
* @var array
*/
public $tasks = [
'Bake.BakeTemplate',
'Bake.Test'
];

/**
* {@inheritDoc}
*/
public function name()
{
return 'action';
}

/**
* {@inheritDoc}
*/
public function fileName($name, $test = false)
{
$fileName = $name . 'Action';
if ($test) {
$fileName .= 'Test';
}
$fileName .= '.php';

return $fileName;
}

/**
* {@inheritDoc}
*/
public function template()
{
return 'HavokInspiration/ActionsClass.action';
}

/**
* {@inheritDoc}
*/
public function templateTest()
{
return 'HavokInspiration/ActionsClass.test';
}

/**
* {@inheritDoc}
*/
public function bake($name)
{
if (strpos($name, '/') === false) {
$this->err('You must pass a Controller name for your action in the format `ControllerName/ActionName`');

return (string)Shell::CODE_ERROR;
}

$this->out("\n" . sprintf('Baking action class for %s...', $name), 1, Shell::QUIET);

list($controller, $action) = $this->getName($name);

$namespace = Configure::read('App.namespace');
if ($this->plugin) {
$namespace = $this->_pluginNamespace($this->plugin);
}

$prefix = $this->_getPrefix();
if ($prefix) {
$prefix = '\\' . str_replace('/', '\\', $prefix);
}

$data = [
'action' => $action,
'controller' => $controller,
'namespace' => $namespace,
'prefix' => $prefix
];

$out = $this->bakeAction($action, $data);

if (!isset($this->params['no-test']) || $this->params['no-test'] !== true) {
$this->bakeActionTest($action, $data);
}

return $out;
}

/**
* Generate the action code
*
* @param string $actionName The name of the action.
* @param array $data The data to turn into code.
* @return string The generated action file.
*/
public function bakeAction($actionName, array $data)
{
$data += [
'namespace' => null,
'controller' => null,
'prefix' => null,
'actions' => null,
];
$this->BakeTemplate->set($data);
$contents = $this->BakeTemplate->generate($this->template());
$path = $this->getPath();
$filename = $path . $data['controller'] . DS . $this->fileName($actionName);
$this->createFile($filename, $contents);

return $contents;
}

/**
* Assembles and writes a unit test file
*
* @param string $className Controller class name
* @return string|null Baked test
*/
public function bakeActionTest($actionName, $data)
{
$data += [
'namespace' => null,
'controller' => null,
'prefix' => null,
'actions' => null,
];
$this->Test->plugin = $this->plugin;
$this->BakeTemplate->set($data);

$prefix = $this->_getPrefix();
$contents = $this->BakeTemplate->generate($this->templateTest());

$path = $this->Test->getPath() . 'Controller' . DS;
if ($prefix) {
$path .= $prefix . DS;
}
$path .= $data['controller'];

$filename = $path . DS . $this->fileName($actionName, true);
$this->createFile($filename, $contents);

return $contents;
}

/**
* Transform the name parameter into Controller & Action name.
*
* @param string $name Name passed to the CLI.
* @return array First key is the controller name, second key the action name.
*/
protected function getName($name)
{
list($controller, $action) = explode('/', $name);

$controller = $this->_camelize($controller);
$action = $this->_camelize($action);

return [$controller, $action];
}

/**
* {@inheritDoc}
*/
public function getOptionParser()
{
$parser = parent::getOptionParser();

$parser
->setDescription(
'Bake an Action class file skeleton'
)
->addOption('prefix', [
'help' => 'The namespace/routing prefix to use.'
])
->addOption('no-test', [
'boolean' => true,
'help' => 'Do not generate a test skeleton.'
]);

return $parser;
}
}
24 changes: 24 additions & 0 deletions src/Template/Bake/action.ctp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
namespace <%= $namespace %>\Controller<%= $prefix %>\<%= $controller %>;

use HavokInspiration\ActionsClass\Controller\Action;

/**
* Controller : <%= $controller %>
* Action : <%= $action %>
*
* @package <%= $namespace %>\Controller
*/
class <%= $action %>Action extends Action
{
/**
* This method will be executed when the `<%= $controller %>` Controller action `<%= $action %>` will be invoked.
* It is the equivalent of the `<%= $controller %>Controller::<%= $action %>()` method.
*
* @return void|\Cake\Network\Response
*/
public function execute()
{

}
}
22 changes: 22 additions & 0 deletions src/Template/Bake/test.ctp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
namespace <%= $namespace %>\Test\TestCase\Controller<%= $prefix %>\<%= $controller %>;

use Cake\TestSuite\IntegrationTestCase;

/**
* Controller <%= $controller %>
* Action <%= $action %>
*
* @package <%= $namespace %>\Controller
*/
class <%= $action %>ActionTest extends IntegrationTestCase
{
/**
* TestCase for \<%= $namespace %>\Controller\<%= $controller %>\<%= $action %>Action
*/
public function test<%= $action %>Action()
{
$this->get('<%= str_replace('\\', '/', strtolower($prefix)) %>/<%= strtolower($controller) %>/<%= strtolower($action) %>');
$this->assertResponseOk();
}
}
57 changes: 57 additions & 0 deletions tests/PHPStan/BakeTemplatePropertyReflection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
namespace HavokInspiration\ActionsClass\PHPStan;

use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\PropertyReflection;
use PHPStan\Type\Type;

class BakeTemplatePropertyReflection implements PropertyReflection
{
/** @var \PHPStan\Reflection\ClassReflection */
private $declaringClass;

/** @var \PHPStan\Type\Type */
private $type;

public function __construct(
ClassReflection $declaringClass,
Type $type
) {
$this->declaringClass = $declaringClass;
$this->type = $type;
}
public function getDeclaringClass(): ClassReflection
{
return $this->declaringClass;
}

public function isStatic(): bool
{
return false;
}

public function isPrivate(): bool
{
return false;
}

public function isPublic(): bool
{
return true;
}

public function getType(): Type
{
return $this->type;
}

public function isReadable(): bool
{
return true;
}

public function isWritable(): bool
{
return true;
}
}
Loading

0 comments on commit c9aac48

Please sign in to comment.