From fdae3ca0d61d38565b31d20a25bbb773f5e26fbb Mon Sep 17 00:00:00 2001 From: "Paul M. Jones" Date: Mon, 30 Dec 2019 11:26:47 -0600 Subject: [PATCH] add 'create action class from verb and path' functionality --- README.md | 58 ++++++++++++++++++++ bin/autoroute-create.php | 62 +++++++++++++++++++++ bin/autoroute-dump.php | 4 +- resources/templates/action.tpl | 9 ++++ src/AutoRoute.php | 12 +++++ src/Creator.php | 98 ++++++++++++++++++++++++++++++++++ tests/CreatorTest.php | 46 ++++++++++++++++ 7 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 bin/autoroute-create.php create mode 100644 resources/templates/action.tpl create mode 100644 src/Creator.php create mode 100644 tests/CreatorTest.php diff --git a/README.md b/README.md index a0239ab..90ab13a 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 diff --git a/bin/autoroute-create.php b/bin/autoroute-create.php new file mode 100644 index 0000000..3cd2e78 --- /dev/null +++ b/bin/autoroute-create.php @@ -0,0 +1,62 @@ +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); diff --git a/bin/autoroute-dump.php b/bin/autoroute-dump.php index e9ddfab..dfcde89 100644 --- a/bin/autoroute-dump.php +++ b/bin/autoroute-dump.php @@ -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); } diff --git a/resources/templates/action.tpl b/resources/templates/action.tpl new file mode 100644 index 0000000..a7e99f9 --- /dev/null +++ b/resources/templates/action.tpl @@ -0,0 +1,9 @@ +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( diff --git a/src/Creator.php b/src/Creator.php new file mode 100644 index 0000000..95be882 --- /dev/null +++ b/src/Creator.php @@ -0,0 +1,98 @@ +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) + ]; + } +} diff --git a/tests/CreatorTest.php b/tests/CreatorTest.php new file mode 100644 index 0000000..62dc237 --- /dev/null +++ b/tests/CreatorTest.php @@ -0,0 +1,46 @@ +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 = 'assertSame($expect, $code); + } +}