Skip to content

Commit

Permalink
Merge pull request #34 from roelofr/feature/custom-alias-improvements
Browse files Browse the repository at this point in the history
Improved custom namespace support
  • Loading branch information
Naoray authored Aug 31, 2021
2 parents bf155ff + 74cd7f7 commit 83cc78b
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 13 deletions.
54 changes: 43 additions & 11 deletions src/NovaGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\Pipeline\Pipeline;
use Illuminate\Support\Str;
use Naoray\BlueprintNovaAddon\Contracts\Task;
use Naoray\BlueprintNovaAddon\Tasks\AddResourceImportIfRequired;
use Naoray\BlueprintNovaAddon\Tasks\AddTimestampFields;
use Naoray\BlueprintNovaAddon\Tasks\RemapImports;

Expand Down Expand Up @@ -54,23 +55,27 @@ public function output(Tree $tree): array

protected function getPath(Model $model): string
{
$path = str_replace('\\', '/', Blueprint::relativeNamespace($this->getNovaNamespace($model).'/'.$model->name()));
$path = str_replace('\\', '/', Blueprint::relativeNamespace($this->getNovaResourceClassName($model)));

return config('blueprint.app_path').'/'.$path.'.php';
}

protected function populateStub(string $stub, Model $model): string
{
$resourceClassName = $this->getNovaResourceClassName($model);
$resourceNamespace = Str::beforeLast($resourceClassName, '\\');

$data = resolve(Pipeline::class)
->send([
'fields' => '',
'imports' => [],
'model' => $model,
'resource' => $resourceClassName,
])
->through($this->filteredTasks())
->thenReturn();

$stub = str_replace('DummyNamespace', $this->getNovaNamespace($model), $stub);
$stub = str_replace('DummyNamespace', $resourceNamespace, $stub);
$stub = str_replace('DummyClass', $model->name(), $stub);
$stub = str_replace('DummyModel', '\\'.$model->fullyQualifiedClassName(), $stub);
$stub = str_replace('// fields...', $data['fields'], $stub);
Expand All @@ -79,19 +84,40 @@ protected function populateStub(string $stub, Model $model): string
return $stub;
}

protected function getNovaNamespace(Model $model): string
/**
* Resolves a classname to a part of Nova, where componentType is the type as used in the
* config, and the localClassName is the relative path to a class from it's defined namespace.
*
* @example For a model with FQCN `App\User`, you'd call getNovaNamespaceFor('resource', 'User') to resolve the path to this
* Model's Resource.
* @example For a model with FQCN `App\Models\Assets\Video`, with Blueprint configured for Laravel 8, you'd call `getNovaNamespaceFor('resource', 'Assets\Video')
*
* @param string $type Nova component type, lowercase (e.g. resource, lens, action)
* @param string $localClassName see examples.
* @return string FQCN to the $componentType's class namespace.
*/
protected function getFullyQualifiedClassNameForComponent(string $type, string $localClassName): string
{
$afterSelector = config('blueprint.models_namespace') ?: config('blueprint.namespace');
$namespace = Str::after($model->fullyQualifiedNamespace(), $afterSelector);

$namespace = implode('\\', array_filter([
return implode('\\', array_filter([
config('blueprint.namespace'),
config('nova_blueprint.namespace', 'Nova'),
config('nova_blueprint.resource_namespace', null),
$namespace,
config("nova_blueprint.{$type}_namespace", null),
ltrim($localClassName, '\\'),
]));
}

return ltrim($namespace, '\\');
/**
* Returns the fully qualified class name of the Nova resource for this model.
*
* @param Model $model
* @return string
*/
protected function getNovaResourceClassName(Model $model): string
{
$afterSelector = config("blueprint.models_namespace") ?: config('blueprint.namespace');
$modelBaseClass = Str::after($model->fullyQualifiedClassName(), $afterSelector);

return $this->getFullyQualifiedClassNameForComponent('resource', $modelBaseClass);
}

public function registerTask(Task $task): void
Expand Down Expand Up @@ -119,7 +145,13 @@ protected function filteredTasks(): array
}, ARRAY_FILTER_USE_KEY);
}

return array_merge($tasks, [new RemapImports]);
$additionalTasks = [
new AddResourceImportIfRequired($this->getFullyQualifiedClassNameForComponent('resource', 'Resource')),
new RemapImports
];


return array_merge($tasks, $additionalTasks);
}

public function types(): array
Expand Down
33 changes: 33 additions & 0 deletions src/Tasks/AddResourceImportIfRequired.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Naoray\BlueprintNovaAddon\Tasks;

use Closure;
use Illuminate\Support\Str;
use Naoray\BlueprintNovaAddon\Contracts\Task;

class AddResourceImportIfRequired implements Task
{
private string $resourceBaseClass;

public function __construct(string $resourceBaseClass)
{
$this->resourceBaseClass = $resourceBaseClass;
}

public function handle(array $data, Closure $next): array
{
$baseNamespace = Str::beforeLast($this->resourceBaseClass, '\\');

$targetClass = $data['resource'];
$targetNamespace = Str::beforeLast($targetClass, '\\');

if ($baseNamespace === $targetNamespace) {
return $next($data);
}

$data['imports'][] = "use {$this->resourceBaseClass};";

return $next($data);
}
}
8 changes: 6 additions & 2 deletions src/Tasks/RemapImports.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ public function handle(array $data, Closure $next): array
{
$data['imports'] = collect($data['imports'])
->unique()
->map(function ($type) {
return 'use Laravel\Nova\Fields\\'.$type.';';
->map(function ($import) {
if (preg_match('/use ([A-Z][\w_]+\\\\)+[A-Z][\w_]+;$/', $import)) {
return $import;
}

return 'use Laravel\Nova\Fields\\'.$import.';';
})
->prepend('use Illuminate\Http\Request;')
->sort(function ($a, $b) {
Expand Down
28 changes: 28 additions & 0 deletions tests/NovaGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,34 @@ public function output_respects_namespace_configurations()
$this->assertEquals(['created' => ['app/Admin/Resources/Video.php']], $this->subject->output($tree));
}

/**
* @test
*/
public function output_respects_namespace_configurations_with_nested_components()
{
$this->app['config']->set('nova_blueprint.namespace', 'Admin');
$this->app['config']->set('nova_blueprint.resource_namespace', 'Resources');

$this->files->expects('get')
->with($this->stubPath().DIRECTORY_SEPARATOR.'class.stub')
->andReturn(file_get_contents('stubs/class.stub'));

$this->files->expects('exists')
->with('app/Admin/Resources/Admin')
->andReturnFalse();

$this->files->expects('makeDirectory')
->with('app/Admin/Resources/Admin', 0755, true);

$this->files->expects('put')
->with('app/Admin/Resources/Admin/User.php', $this->fixture('nova/nested-components-custom-namespace.php'));

$tokens = $this->blueprint->parse($this->fixture('definitions/nested-components-custom-namespace.bp'));
$tree = $this->blueprint->analyze($tokens);

$this->assertEquals(['created' => ['app/Admin/Resources/Admin/User.php']], $this->subject->output($tree));
}

public function novaTreeDataProvider()
{
return [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
models:
Admin/User:
name: string
password: string
101 changes: 101 additions & 0 deletions tests/fixtures/nova/nested-components-custom-namespace.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace App\Admin\Resources\Admin;

use Laravel\Nova\Fields\ID;
use Illuminate\Http\Request;
use Laravel\Nova\Fields\Text;
use App\Admin\Resources\Resource;
use Laravel\Nova\Fields\DateTime;

class User extends Resource
{
/**
* The model the resource corresponds to.
*
* @var string
*/
public static $model = \App\Admin\User::class;

/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'id';

/**
* The columns that should be searched.
*
* @var array
*/
public static $search = [
'id',
];

/**
* Get the fields displayed by the resource.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function fields(Request $request)
{
return [
ID::make()->sortable(),

Text::make('Name')
->rules('required', 'string'),

Text::make('Password')
->rules('required', 'password'),

DateTime::make('Created at'),
DateTime::make('Updated at'),
];
}

/**
* Get the cards available for the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function cards(Request $request)
{
return [];
}

/**
* Get the filters available for the resource.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function filters(Request $request)
{
return [];
}

/**
* Get the lenses available for the resource.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function lenses(Request $request)
{
return [];
}

/**
* Get the actions available for the resource.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function actions(Request $request)
{
return [];
}
}

0 comments on commit 83cc78b

Please sign in to comment.