Skip to content

Commit 301a556

Browse files
Feature: invokable controllers (#435)
1 parent b7d20a8 commit 301a556

17 files changed

+144
-16
lines changed

src/Blueprint.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ public function parse($content, $strip_dashes = true)
6565
return $matches[1] . 'resource: web';
6666
}, $content);
6767

68+
$content = preg_replace_callback('/^(\s+)invokable?$/mi', function ($matches) {
69+
return $matches[1].'invokable: true';
70+
}, $content);
71+
6872
$content = preg_replace_callback('/^(\s+)uuid(: true)?$/mi', function ($matches) {
6973
return $matches[1] . 'id: uuid primary';
7074
}, $content);

src/Generators/RouteGenerator.php

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,7 @@ protected function buildRoutes(Controller $controller)
5252
$routes = '';
5353
$methods = array_keys($controller->methods());
5454

55-
$useTuples = config('blueprint.generate_fqcn_route');
56-
57-
$className = $useTuples
58-
? $controller->fullyQualifiedClassName() . '::class'
59-
: '\'' . str_replace('App\Http\Controllers\\', '', $controller->fullyQualifiedClassName()) . '\'';
55+
$className = $this->getClassName($controller);
6056

6157
$slug = Str::kebab($controller->prefix());
6258

@@ -83,17 +79,38 @@ protected function buildRoutes(Controller $controller)
8379

8480
$methods = array_diff($methods, Controller::$resourceMethods);
8581
foreach ($methods as $method) {
86-
if ($useTuples) {
87-
$action = "[{$className}, '{$method}']";
88-
} else {
89-
$classNameNoQuotes = trim($className, '\'');
90-
$action = "'{$classNameNoQuotes}@{$method}'";
91-
}
92-
93-
$routes .= sprintf("Route::get('%s/%s', %s);", $slug, Str::kebab($method), $action);
82+
$routes .= $this->buildRouteLine($className, $slug, $method);
9483
$routes .= PHP_EOL;
9584
}
9685

9786
return trim($routes);
9887
}
88+
89+
protected function useTuples()
90+
{
91+
return config('blueprint.generate_fqcn_route');
92+
}
93+
94+
protected function getClassName(Controller $controller)
95+
{
96+
return $this->useTuples()
97+
? $controller->fullyQualifiedClassName() . '::class'
98+
: '\'' . str_replace('App\Http\Controllers\\', '', $controller->fullyQualifiedClassName()) . '\'';
99+
}
100+
101+
protected function buildRouteLine($className, $slug, $method)
102+
{
103+
if ($method === '__invoke') {
104+
return sprintf("Route::get('%s', %s);", $slug, $className);
105+
}
106+
107+
if ($this->useTuples()) {
108+
$action = "[{$className}, '{$method}']";
109+
} else {
110+
$classNameNoQuotes = trim($className, '\'');
111+
$action = "'{$classNameNoQuotes}@{$method}'";
112+
}
113+
114+
return sprintf("Route::get('%s/%s', %s);", $slug, Str::kebab($method), $action);
115+
}
99116
}

src/Lexers/ControllerLexer.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ public function analyze(array $tokens): array
4242
$definition = array_merge($resource_definition, $definition);
4343
}
4444

45+
if (isset($definition['invokable'])) {
46+
$definition['invokable'] === true
47+
? $definition['__invoke'] = ['render' => Str::camel($this->getControllerModelName($controller))]
48+
: $definition['__invoke'] = $definition['invokable'];
49+
50+
unset($definition['invokable']);
51+
}
52+
4553
foreach ($definition as $method => $body) {
4654
$controller->addMethod($method, $this->statementLexer->analyze($body));
4755
}
@@ -61,7 +69,7 @@ private function generateResourceTokens(Controller $controller, array $methods)
6169
->mapWithKeys(function ($statements, $method) use ($controller) {
6270
return [
6371
str_replace('api.', '', $method) => collect($statements)->map(function ($statement) use ($controller) {
64-
$model = Str::singular($controller->prefix());
72+
$model = $this->getControllerModelName($controller);
6573

6674
return str_replace(
6775
['[singular]', '[plural]'],
@@ -74,6 +82,11 @@ private function generateResourceTokens(Controller $controller, array $methods)
7482
->toArray();
7583
}
7684

85+
private function getControllerModelName(Controller $controller)
86+
{
87+
return Str::singular($controller->prefix());
88+
}
89+
7790
private function resourceTokens()
7891
{
7992
return [

tests/Feature/BlueprintTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ public function it_parses_shorthands()
114114
'Context' => [
115115
'resource' => 'web',
116116
],
117+
'Report' => [
118+
'invokable' => true
119+
],
117120
],
118121
], $this->subject->parse($blueprint));
119122
}

tests/Feature/Generators/ControllerGeneratorTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ public function controllerTreeDataProvider()
209209
['drafts/resource-statements.yaml', 'app/Http/Controllers/UserController.php', 'controllers/resource-statements.php'],
210210
['drafts/save-without-validation.yaml', 'app/Http/Controllers/PostController.php', 'controllers/save-without-validation.php'],
211211
['drafts/api-routes-example.yaml', 'app/Http/Controllers/Api/CertificateController.php', 'controllers/api-routes-example.php'],
212+
['drafts/invokable-controller.yaml', 'app/Http/Controllers/ReportController.php', 'controllers/invokable-controller.php'],
213+
['drafts/invokable-controller-shorthand.yaml', 'app/Http/Controllers/ReportController.php', 'controllers/invokable-controller-shorthand.php'],
212214
];
213215
}
214216
}

tests/Feature/Generators/RouteGeneratorTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ public function controllerTreeDataProvider()
111111
['drafts/cruddy.yaml', 'routes/cruddy.php'],
112112
['drafts/non-cruddy.yaml', 'routes/non-cruddy.php'],
113113
['drafts/respond-statements.yaml', 'routes/respond-statements.php'],
114+
['drafts/invokable-controller.yaml', 'routes/invokable-controller.php'],
115+
['drafts/invokable-controller-shorthand.yaml', 'routes/invokable-controller.php'],
114116
];
115117
}
116118
}

tests/Feature/Lexers/ControllerLexerTest.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,6 @@ public function it_returns_a_resource_controller_with_overrides()
364364

365365
$methods = $controller->methods();
366366
$this->assertCount(3, $methods);
367-
368367
$this->assertCount(1, $methods['index']);
369368
$this->assertEquals('custom-index-statements', $methods['index'][0]);
370369
$this->assertCount(1, $methods['show']);
@@ -421,4 +420,31 @@ public function it_returns_a_resource_controllers_with_api_flag_set()
421420
$this->assertCount(5, $controller->methods());
422421
$this->assertTrue($controller->isApiResource());
423422
}
423+
424+
/**
425+
* @test
426+
*/
427+
public function it_returns_an_invokable_controller()
428+
{
429+
$tokens = [
430+
'controllers' => [
431+
'Report' => [
432+
'__invoke' => [
433+
'render' => 'report'
434+
]
435+
]
436+
]
437+
];
438+
439+
$this->statementLexer->shouldReceive('analyze');
440+
441+
$actual = $this->subject->analyze($tokens);
442+
443+
$this->assertCount(1, $actual['controllers']);
444+
445+
$controller = $actual['controllers']['Report'];
446+
$this->assertEquals('ReportController', $controller->className());
447+
$this->assertCount(1, $controller->methods());
448+
$this->assertFalse($controller->isApiResource());
449+
}
424450
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use Illuminate\Http\Request;
6+
7+
class ReportController extends Controller
8+
{
9+
/**
10+
* @param \Illuminate\Http\Request $request
11+
* @return \Illuminate\Http\Response
12+
*/
13+
public function __invoke(Request $request)
14+
{
15+
return view('report');
16+
}
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Events\ReportGenerated;
6+
use Illuminate\Http\Request;
7+
8+
class ReportController extends Controller
9+
{
10+
/**
11+
* @param \Illuminate\Http\Request $request
12+
* @return \Illuminate\Http\Response
13+
*/
14+
public function __invoke(Request $request)
15+
{
16+
event(new ReportGenerated());
17+
18+
return view('report');
19+
}
20+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
controllers:
2+
Report:
3+
invokable

0 commit comments

Comments
 (0)