Skip to content

Commit 9c29b1d

Browse files
committed
Nested components and configurable namespaces
1 parent 076f226 commit 9c29b1d

40 files changed

+2748
-230
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
},
1616
"require-dev": {
1717
"phpunit/phpunit": "^8.0",
18-
"mockery/mockery": "^1.2"
18+
"mockery/mockery": "^1.2",
19+
"orchestra/testbench": "^4.0"
1920
},
2021
"autoload": {
2122
"psr-4": {

composer.lock

Lines changed: 1962 additions & 184 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/blueprint.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
return [
4+
5+
/*
6+
|--------------------------------------------------------------------------
7+
| Application Namespace
8+
|--------------------------------------------------------------------------
9+
|
10+
| Blueprint assumes a default Laravel application namespace of 'App'.
11+
| However, you may configure Blueprint to use a custom namespace.
12+
| Ultimately this should match the PSR-4 autoload value set
13+
| within the composer.json file of your application.
14+
|
15+
*/
16+
'namespace' => 'App',
17+
18+
19+
/*
20+
|--------------------------------------------------------------------------
21+
| Component Namespaces
22+
|--------------------------------------------------------------------------
23+
|
24+
| Blueprint promotes following Laravel conventions. As such, it generates
25+
| components under the default namespaces. For example, models are under
26+
| the `App` namespace. However, you may configure Blueprint to use
27+
| a custom namespace when generating these components.
28+
|
29+
*/
30+
'models_namespace' => '',
31+
'controllers_namespace' => 'Http\\Controllers',
32+
33+
34+
/*
35+
|--------------------------------------------------------------------------
36+
| Application Path
37+
|--------------------------------------------------------------------------
38+
|
39+
| Here you may customize the path where Blueprint stores generated
40+
| components. By default, Blueprint will store files under the
41+
| `app` folder However, you may change the path to store
42+
| generated component elsewhere.
43+
|
44+
*/
45+
'app_path' => app_path(),
46+
47+
];

src/Blueprint.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ class Blueprint
1111
private $lexers = [];
1212
private $generators = [];
1313

14+
public static function relativeNamespace(string $fullyQualifiedClassName)
15+
{
16+
return ltrim(str_replace(config('blueprint.namespace'), '', $fullyQualifiedClassName), '\\');
17+
}
18+
1419
public function parse($content)
1520
{
1621
$content = preg_replace_callback('/^(\s+)(id|timestamps(Tz)?|softDeletes(Tz)?)$/mi', function ($matches) {

src/BlueprintServiceProvider.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ public function boot()
1717
if (!defined('STUBS_PATH')) {
1818
define('STUBS_PATH', dirname(__DIR__) . '/stubs');
1919
}
20+
21+
$this->publishes([
22+
__DIR__.'/../config/blueprint.php' => config_path('blueprint.php'),
23+
]);
2024
}
2125

2226
/**
@@ -26,6 +30,10 @@ public function boot()
2630
*/
2731
public function register()
2832
{
33+
$this->mergeConfigFrom(
34+
__DIR__.'/../config/blueprint.php', 'blueprint'
35+
);
36+
2937
$this->app->bind('command.blueprint.build',
3038
function ($app) {
3139
return new BlueprintCommand($app['files']);

src/Generators/ControllerGenerator.php

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Blueprint\Generators;
44

5+
use Blueprint\Blueprint;
56
use Blueprint\Contracts\Generator;
67
use Blueprint\Models\Controller;
78
use Blueprint\Models\Statements\DispatchStatement;
@@ -52,7 +53,7 @@ public function output(array $tree): array
5253

5354
protected function populateStub(string $stub, Controller $controller)
5455
{
55-
$stub = str_replace('DummyNamespace', 'App\\Http\\Controllers', $stub);
56+
$stub = str_replace('DummyNamespace', $controller->fullyQualifiedNamespace(), $stub);
5657
$stub = str_replace('DummyClass', $controller->className(), $stub);
5758
$stub = str_replace('// methods...', $this->buildMethods($controller), $stub);
5859
$stub = str_replace('// imports...', $this->buildImports($controller), $stub);
@@ -71,7 +72,7 @@ private function buildMethods(Controller $controller)
7172

7273
if (in_array($name, ['edit', 'update', 'show', 'destroy'])) {
7374
$context = Str::singular($controller->prefix());
74-
$reference = 'App\\' . $context;
75+
$reference = config('blueprint.namespace') . '\\' . $context;
7576
$variable = '$' . Str::camel($context);
7677

7778
// TODO: verify controller prefix references a model
@@ -88,21 +89,23 @@ private function buildMethods(Controller $controller)
8889
if ($statement instanceof SendStatement) {
8990
$body .= self::INDENT . $statement->output() . PHP_EOL;
9091
$this->addImport($controller, 'Illuminate\\Support\\Facades\\Mail');
91-
$this->addImport($controller, 'App\\Mail\\' . $statement->mail());
92+
$this->addImport($controller, config('blueprint.namespace') . '\\Mail\\' . $statement->mail());
9293
} elseif ($statement instanceof ValidateStatement) {
93-
$class = $controller->name() . Str::studly($name) . 'Request';
94+
$class_name = $controller->name() . Str::studly($name) . 'Request';
9495

95-
$method = str_replace('\Illuminate\Http\Request $request', '\\App\\Http\\Requests\\' . $class . ' $request', $method);
96-
$method = str_replace('(Request $request', '(' . $class . ' $request', $method);
96+
$fqcn = config('blueprint.namespace') . '\\Http\\Requests\\' . ($controller->namespace() ? $controller->namespace() . '\\' : '') . $class_name;
9797

98-
$this->addImport($controller, 'App\\Http\\Requests\\' . $class);
98+
$method = str_replace('\Illuminate\Http\Request $request', '\\' . $fqcn . ' $request', $method);
99+
$method = str_replace('(Request $request', '(' . $class_name . ' $request', $method);
100+
101+
$this->addImport($controller, $fqcn);
99102
} elseif ($statement instanceof DispatchStatement) {
100103
$body .= self::INDENT . $statement->output() . PHP_EOL;
101-
$this->addImport($controller, 'App\\Jobs\\' . $statement->job());
104+
$this->addImport($controller, config('blueprint.namespace') . '\\Jobs\\' . $statement->job());
102105
} elseif ($statement instanceof FireStatement) {
103106
$body .= self::INDENT . $statement->output() . PHP_EOL;
104107
if (!$statement->isNamedEvent()) {
105-
$this->addImport($controller, 'App\\Events\\' . $statement->event());
108+
$this->addImport($controller, config('blueprint.namespace') . '\\Events\\' . $statement->event());
106109
}
107110
} elseif ($statement instanceof RenderStatement) {
108111
$body .= self::INDENT . $statement->output() . PHP_EOL;
@@ -112,10 +115,10 @@ private function buildMethods(Controller $controller)
112115
$body .= self::INDENT . $statement->output() . PHP_EOL;
113116
} elseif ($statement instanceof EloquentStatement) {
114117
$body .= self::INDENT . $statement->output($controller->prefix(), $name) . PHP_EOL;
115-
$this->addImport($controller, 'App\\' . $this->determineModel($controller->prefix(), $statement->reference()));
118+
$this->addImport($controller, config('blueprint.namespace') . '\\' . ($controller->namespace() ? $controller->namespace() . '\\' : '') . $this->determineModel($controller, $statement->reference()));
116119
} elseif ($statement instanceof QueryStatement) {
117120
$body .= self::INDENT . $statement->output($controller->prefix()) . PHP_EOL;
118-
$this->addImport($controller, 'App\\' . $this->determineModel($controller->prefix(), $statement->model()));
121+
$this->addImport($controller, config('blueprint.namespace') . '\\' . ($controller->namespace() ? $controller->namespace() . '\\' : '') . $this->determineModel($controller, $statement->model()));
119122
}
120123

121124
$body .= PHP_EOL;
@@ -133,7 +136,9 @@ private function buildMethods(Controller $controller)
133136

134137
protected function getPath(Controller $controller)
135138
{
136-
return 'app/Http/Controllers/' . $controller->className() . '.php';
139+
$path = str_replace('\\', '/', Blueprint::relativeNamespace($controller->fullyQualifiedClassName()));
140+
141+
return config('blueprint.app_path') . '/' . $path . '.php';
137142
}
138143

139144
private function methodStub()
@@ -162,10 +167,10 @@ private function buildImports(Controller $controller)
162167
}, $imports));
163168
}
164169

165-
private function determineModel(string $prefix, ?string $reference)
170+
private function determineModel(Controller $controller, ?string $reference)
166171
{
167172
if (empty($reference) || $reference === 'id') {
168-
return Str::studly(Str::singular($prefix));
173+
return Str::studly(Str::singular($controller->prefix()));
169174
}
170175

171176
if (Str::contains($reference, '.')) {

src/Generators/FactoryGenerator.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,17 @@ public function output(array $tree): array
4040

4141
protected function getPath(Model $model)
4242
{
43-
return 'database/factories/' . $model->name() . 'Factory.php';
43+
$path = $model->name();
44+
if ($model->namespace()) {
45+
$path = str_replace('\\', '/', $model->namespace()) . '/' . $path;
46+
}
47+
48+
return 'database/factories/' . $path . 'Factory.php';
4449
}
4550

4651
protected function populateStub(string $stub, Model $model)
4752
{
48-
$stub = str_replace('DummyNamespace', 'App', $stub);
53+
$stub = str_replace('DummyModel', $model->fullyQualifiedClassName(), $stub);
4954
$stub = str_replace('DummyClass', $model->name(), $stub);
5055
$stub = str_replace('// definition...', $this->buildDefinition($model), $stub);
5156

@@ -67,7 +72,7 @@ protected function buildDefinition(Model $model)
6772
$class = Str::studly($column->attributes()[0] ?? $name);
6873

6974
$definition .= self::INDENT . "'{$column->name()}' => ";
70-
$definition .= sprintf("factory(\App\%s::class)", $class);
75+
$definition .= sprintf("factory(%s::class)", '\\' . $model->fullyQualifiedNamespace() . '\\' . $class);
7176
$definition .= ',' . PHP_EOL;
7277
} else {
7378
$definition .= self::INDENT . "'{$column->name()}' => ";

src/Generators/ModelGenerator.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Blueprint\Generators;
44

5+
use Blueprint\Blueprint;
56
use Blueprint\Contracts\Generator;
67
use Blueprint\Models\Column;
78
use Blueprint\Models\Model;
@@ -39,7 +40,7 @@ public function output(array $tree): array
3940

4041
protected function populateStub(string $stub, Model $model)
4142
{
42-
$stub = str_replace('DummyNamespace', 'App', $stub);
43+
$stub = str_replace('DummyNamespace', $model->fullyQualifiedNamespace(), $stub);
4344
$stub = str_replace('DummyClass', $model->name(), $stub);
4445

4546
$body = $this->buildProperties($model);
@@ -93,7 +94,7 @@ private function buildRelationships(Model $model)
9394
foreach ($columns as $column) {
9495
$name = Str::beforeLast($column->name(), '_id');
9596
$class = Str::studly($column->attributes()[0] ?? $name);
96-
$relationship = sprintf("\$this->belongsTo(\App\%s::class)", $class);
97+
$relationship = sprintf("\$this->belongsTo(%s::class)", '\\' . $model->fullyQualifiedNamespace() . '\\' . $class);
9798

9899
$method = str_replace('DummyName', Str::camel($name), $template);
99100
$method = str_replace('null', $relationship, $method);
@@ -106,7 +107,9 @@ private function buildRelationships(Model $model)
106107

107108
protected function getPath(Model $model)
108109
{
109-
return 'app/' . $model->name() . '.php';
110+
$path = str_replace('\\', '/', Blueprint::relativeNamespace($model->fullyQualifiedClassName()));
111+
112+
return config('blueprint.app_path') . '/' . $path . '.php';
110113
}
111114

112115
private function fillableColumns(array $columns)

src/Generators/Statements/EventGenerator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ public function output(array $tree): array
5959

6060
protected function getPath(string $name)
6161
{
62-
return 'app/Events/' . $name . '.php';
62+
return config('blueprint.app_path') . '/Events/' . $name . '.php';
6363
}
6464

6565
protected function populateStub(string $stub, FireStatement $fireStatement)
6666
{
67-
$stub = str_replace('DummyNamespace', 'App\\Events', $stub);
67+
$stub = str_replace('DummyNamespace', config('blueprint.namespace') . '\\Events', $stub);
6868
$stub = str_replace('DummyClass', $fireStatement->event(), $stub);
6969
$stub = str_replace('// properties...', $this->buildConstructor($fireStatement), $stub);
7070

src/Generators/Statements/FormRequestGenerator.php

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Blueprint\Generators\Statements;
44

55
use Blueprint\Contracts\Generator;
6+
use Blueprint\Models\Controller;
67
use Blueprint\Models\Statements\ValidateStatement;
78
use Blueprint\Translators\Rules;
89
use Illuminate\Support\Str;
@@ -41,7 +42,7 @@ public function output(array $tree): array
4142

4243
$context = Str::singular($controller->prefix());
4344
$name = $this->getName($context, $method);
44-
$path = $this->getPath($name);
45+
$path = $this->getPath($controller, $name);
4546

4647
if ($this->files->exists($path)) {
4748
continue;
@@ -53,7 +54,7 @@ public function output(array $tree): array
5354

5455
$this->files->put(
5556
$path,
56-
$this->populateStub($stub, $name, $context, $statement)
57+
$this->populateStub($stub, $name, $context, $statement, $controller)
5758
);
5859

5960
$output['created'][] = $path;
@@ -64,14 +65,14 @@ public function output(array $tree): array
6465
return $output;
6566
}
6667

67-
protected function getPath(string $name)
68+
protected function getPath(Controller $controller, string $name)
6869
{
69-
return 'app/Http/Requests/' . $name . '.php';
70+
return config('blueprint.app_path') . '/Http/Requests/' . ($controller->namespace() ? $controller->namespace() . '/' : '') . $name . '.php';
7071
}
7172

72-
protected function populateStub(string $stub, string $name, $context, ValidateStatement $validateStatement)
73+
protected function populateStub(string $stub, string $name, $context, ValidateStatement $validateStatement, Controller $controller)
7374
{
74-
$stub = str_replace('DummyNamespace', 'App\\Http\\Requests', $stub);
75+
$stub = str_replace('DummyNamespace', config('blueprint.namespace') . '\\Http\\Requests' . ($controller->namespace() ? '\\' . $controller->namespace() : ''), $stub);
7576
$stub = str_replace('DummyClass', $name, $stub);
7677
$stub = str_replace('// rules...', $this->buildRules($context, $validateStatement), $stub);
7778

@@ -96,7 +97,19 @@ private function buildRules(string $context, ValidateStatement $validateStatemen
9697

9798
private function modelForContext(string $context)
9899
{
99-
return $this->models[Str::studly($context)] ?? $this->models[Str::lower($context)] ?? null;
100+
if (isset($this->models[Str::studly($context)])) {
101+
return $this->models[Str::studly($context)];
102+
}
103+
104+
$matches = array_filter(array_keys($this->models), function ($key) use ($context) {
105+
return Str::endsWith($key, '/' . Str::studly($context));
106+
});
107+
108+
if (count($matches) === 1) {
109+
return $this->models[$matches[0]];
110+
}
111+
112+
return null;
100113
}
101114

102115
private function getName(string $context, string $method)

0 commit comments

Comments
 (0)