Skip to content

Commit

Permalink
add 'create action class from verb and path' functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul M. Jones committed Dec 30, 2019
1 parent 494658a commit fdae3ca
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 2 deletions.
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ cases -- even when FastRoute is using cached route definitions.
- [Generating Route Paths](#generating-route-paths)
- [Alternative Configurations](#alternative-configurations)
- [Dumping All Routes](#dumping-all-routes)
- [Creating Classes From Routes](#creating-classes-from-routes)
- [Questions and Recipes](#questions-and-recipes)


Expand Down Expand Up @@ -461,6 +462,63 @@ You can specify alternative configurations with these command line options:
- `--suffix=` to note a standard action class suffix
- `--word-separator=` to specify an alternative word separator


## Creating Classes From Routes

_AutoRoute_ provides minimalist support for creating class files based on a
route verb and path, using a template.

To do so, invoke `autoroute-create.php` with the base namespace, the directory
for that namespace, the HTTP verb, and the URL path with parameter token
placeholders.

For example, the following command ...

```
$ php bin/autoroute-create.php App\\Http ./src/Http GET /photo/{photoId}
```

... will create this class file at `./src/Http/Photo/GetPhoto.php`:

```php
namespace App\Http\Photo;

class GetPhoto
{
public function __invoke($photoId)
{
}
}
```

The command will not overwrite existing files.

You can specify alternative configurations with these command line options:

- `--method=` to set the action class method name
- `--suffix=` to note a standard action class suffix
- `--template=` to specify the path to a custom template
- `--word-separator=` to specify an alternative word separator

The default class template file is `resources/templates/action.tpl`. If you
decide to write a custom template of your own, the available string-replacement
placeholders are:

- `{NAMESPACE}`
- `{CLASS}`
- `{METHOD}`
- `{PARAMETERS}`

These names should be self-explanatory.

> **Note:**
>
> Even with a custom template, you will almost certainly need to edit the new
> file to add a constructor, typehints, default values, and so on. The file
> creation functionality is necessarily minimalist, and cannot account for all
> possible variability in your specific situation.

## Questions and Recipes

### Child Resources
Expand Down
62 changes: 62 additions & 0 deletions bin/autoroute-create.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php
use AutoRoute\AutoRoute;

$autoload = require dirname(__DIR__) . '/vendor/autoload.php';

$options = getopt('', ['method:', 'suffix:', 'template:', 'word-separator:'], $optind);

$namespace = $argv[$optind + 0] ?? null;
if ($namespace === null) {
echo "Please pass a PHP namespace as the first argument." . PHP_EOL;
exit(1);
}

$directory = realpath($argv[$optind + 1] ?? null);
if ($directory === false) {
echo "Please pass the PHP namespace directory path as the second argument." . PHP_EOL;
exit(1);
}

$verb = $argv[$optind + 2] ?? null;
if ($verb === null) {
echo "Please pass an HTTP verb as the third argument." . PHP_EOL;
exit(1);
}

$path = $argv[$optind + 3] ?? null;
if ($path === null) {
echo "Please pass a URL path as the fourth argument." . PHP_EOL;
exit(1);
}

$template = $options['template'] ?? dirname(__DIR__) . '/resources/templates/action.tpl';
if (! file_exists($template)) {
echo "Template file {$template} does not exist." . PHP_EOL;
exit(1);
}

// ---

$autoRoute = (new AutoRoute($namespace, $directory))
->setMethod($options['method'] ?? '__invoke')
->setSuffix($options['suffix'] ?? '')
->setWordSeparator($options['word-separator'] ?? '-');

$creator = $autoRoute->newCreator(file_get_contents($template));

[$file, $code] = $creator->create($verb, $path);

echo $file . PHP_EOL;
if (file_exists($file)) {
echo "Already exists; not overwriting." . PHP_EOL;
exit(1);
}

$dir = dirname($file);
if (! is_dir($dir)) {
mkdir($dir, 0777, true);
}

file_put_contents($file, $code);

exit(0);
4 changes: 2 additions & 2 deletions bin/autoroute-dump.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

$namespace = $argv[$optind + 0] ?? null;
if ($namespace === null) {
echo "Please pass a PHP namespace as the first argument.";
echo "Please pass a PHP namespace as the first argument." . PHP_EOL;
exit(1);
}

$directory = realpath($argv[$optind + 1] ?? null);
if ($directory === false) {
echo "Please pass the PHP namespace directory path as the second argument.";
echo "Please pass the PHP namespace directory path as the second argument." . PHP_EOL;
exit(1);
}

Expand Down
9 changes: 9 additions & 0 deletions resources/templates/action.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php
namespace {NAMESPACE};

class {CLASS}
{
public function {METHOD}({PARAMETERS})
{
}
}
12 changes: 12 additions & 0 deletions src/AutoRoute.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ public function newDumper()
return new Dumper($this->newActions());
}

public function newCreator(string $template)
{
return new Creator(
$this->namespace,
$this->directory,
$this->suffix,
$this->method,
$this->wordSeparator,
$template
);
}

protected function newActions()
{
return new Actions(
Expand Down
98 changes: 98 additions & 0 deletions src/Creator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php
/**
*
* This file is part of AutoRoute for PHP.
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
declare(strict_types=1);

namespace AutoRoute;

class Creator
{
protected $namespace;

protected $directory;

protected $suffix;

protected $method;

protected $wordSeparator;

protected $template;

public function __construct(
string $namespace,
string $directory,
string $suffix,
string $method,
string $wordSeparator,
string $template
) {
$this->namespace = trim($namespace, '\\') . '\\';
$this->directory = rtrim($directory, DIRECTORY_SEPARATOR);
$this->suffix = $suffix;
$this->method = $method;
$this->wordSeparator = $wordSeparator;
$this->template = $template;
}

public function create(string $verb, string $path) : array
{
[$namespace, $directory, $class, $parameters] = $this->parse($verb, $path);
$file = "{$directory}/{$class}.php";
$vars = [
'{NAMESPACE}' => $namespace,
'{CLASS}' => $class,
'{METHOD}' => $this->method,
'{PARAMETERS}' => $parameters,
];
$code = strtr($this->template, $vars);
return [$file, $code];
}

protected function parse(string $verb, string $path) : array
{
$segments = explode('/', trim($path, '/'));
$subNamespaces = [];
$parameters = [];

while (! empty($segments)) {
$segment = array_shift($segments);

if (substr($segment, 0, 1) == '{') {
$parameters[] = '$' . trim($segment, '{} ');
continue;
}

$segment = str_replace($this->wordSeparator, ' ', $segment);
$segment = str_replace(' ', '', ucwords($segment));
$subNamespaces[] = $segment;
}

$namespace = '';

foreach ($subNamespaces as $subNamespace) {
$namespace .= ucfirst(strtolower($subNamespace)) . '\\';
}

$namespace = rtrim($namespace, '\\');

$class = ucfirst(strtolower($verb))
. str_replace('\\', '', $namespace)
. $this->suffix;

$directory = $this->directory . DIRECTORY_SEPARATOR
. str_replace('\\', DIRECTORY_SEPARATOR, $namespace);

return [
rtrim($this->namespace . $namespace, '\\'),
$directory,
$class,
implode(', ', $parameters)
];
}
}
46 changes: 46 additions & 0 deletions tests/CreatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);

namespace AutoRoute;

class CreatorTest extends \PHPUnit\Framework\TestCase
{
public function test()
{
$autoRoute = new AutoRoute(
'AutoRoute\\Http',
__DIR__ . DIRECTORY_SEPARATOR . 'Http'
);
$autoRoute->setSuffix('Action');
$autoRoute->setMethod('exec');
$creator = $autoRoute->newCreator(file_get_contents(
dirname(__DIR__) . '/resources/templates/action.tpl'
));

[$file, $code] = $creator->create(
'GET',
'/company/{companyId}/employee/{employeeNum}'
);

$expect = str_replace(
'/',
DIRECTORY_SEPARATOR,
__DIR__ . DIRECTORY_SEPARATOR . 'Http/Company/Employee/GetCompanyEmployeeAction.php'
);

$this->assertSame($expect, $file);

$expect = '<?php
namespace AutoRoute\Http\Company\Employee;
class GetCompanyEmployeeAction
{
public function exec($companyId, $employeeNum)
{
}
}
';

$this->assertSame($expect, $code);
}
}

0 comments on commit fdae3ca

Please sign in to comment.