diff --git a/routes/api.php b/routes/api.php index 0861051b..b7495536 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,14 +1,7 @@ except(['store']); - -// Resource Fields -Route::any('/{resource}/fields/{field}', ResourceFieldController::class)->where('field', '.*'); - -// Actions -// Action Fields diff --git a/src/Actions/Action.php b/src/Actions/Action.php index e454c285..28265af8 100644 --- a/src/Actions/Action.php +++ b/src/Actions/Action.php @@ -3,16 +3,19 @@ namespace Cone\Root\Actions; use Cone\Root\Fields\Field; +use Cone\Root\Http\Controllers\ActionController; use Cone\Root\Interfaces\Form; use Cone\Root\Support\Alert; use Cone\Root\Traits\AsForm; use Cone\Root\Traits\Authorizable; use Cone\Root\Traits\Makeable; +use Cone\Root\Traits\RegistersRoutes; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Concerns\HasAttributes; use Illuminate\Http\Request; +use Illuminate\Routing\Router; use Illuminate\Support\Facades\Redirect; use Illuminate\Support\MessageBag; use Illuminate\Support\Str; @@ -25,6 +28,9 @@ abstract class Action implements Arrayable, Form, JsonSerializable use Authorizable; use HasAttributes; use Makeable; + use RegistersRoutes { + RegistersRoutes::registerRoutes as __registerRoutes; + } /** * The Blade template. @@ -46,11 +52,6 @@ abstract class Action implements Arrayable, Form, JsonSerializable */ protected ?Builder $query = null; - /** - * The API URI. - */ - protected ?string $apiUri = null; - /** * Handle the action. */ @@ -96,24 +97,6 @@ public function getTemplate(): string return $this->template; } - /** - * Set the API URI. - */ - public function setApiUri(string $apiUri): static - { - $this->apiUri = $apiUri; - - return $this; - } - - /** - * Get the API URI. - */ - public function getApiUri(): ?string - { - return $this->apiUri; - } - /** * Set the Eloquent query. */ @@ -129,7 +112,6 @@ public function setQuery(Builder $query): static */ protected function resolveField(Request $request, Field $field): void { - $field->setApiUri(sprintf('/%s/fields/%s', $this->getApiUri(), $field->getUriKey())); $field->setAttribute('form', $this->getKey()); $field->resolveErrorsUsing(function (Request $request): MessageBag { return $this->errors($request); @@ -190,6 +172,26 @@ public function perform(Request $request): Response ); } + /** + * Register the routes using the given router. + */ + public function registerRoutes(Request $request, Router $router): void + { + $this->__registerRoutes($request, $router); + + $router->prefix($this->getUriKey())->group(function (Router $router) use ($request): void { + $this->resolveFields($request)->registerRoutes($request, $router); + }); + } + + /** + * The routes that should be registered. + */ + public function routes(Router $router): void + { + $router->post('/', ActionController::class); + } + /** * Convert the element to a JSON serializable format. */ @@ -210,7 +212,7 @@ public function toArray(): array 'modalKey' => $this->getModalKey(), 'name' => $this->getName(), 'template' => $this->getTemplate(), - 'url' => $this->getApiUri(), + 'url' => $this->getUri(), ]; } diff --git a/src/Actions/Actions.php b/src/Actions/Actions.php index c7014b0a..98ef80f8 100644 --- a/src/Actions/Actions.php +++ b/src/Actions/Actions.php @@ -2,7 +2,9 @@ namespace Cone\Root\Actions; +use Cone\Root\Traits\RegistersRoutes; use Illuminate\Http\Request; +use Illuminate\Routing\Router; use Illuminate\Support\Arr; use Illuminate\Support\Collection; @@ -27,4 +29,18 @@ public function mapToTableComponents(Request $request): array { return $this->map->toTableComponent($request)->all(); } + + /** + * Register the action routes. + */ + public function registerRoutes(Request $request, Router $router): void + { + $router->prefix('actions')->group(function (Router $router) use ($request): void { + $this->each(static function (Action $action) use ($request, $router): void { + if (in_array(RegistersRoutes::class, class_uses_recursive($action))) { + $action->registerRoutes($request, $router); + } + }); + }); + } } diff --git a/src/Fields/BelongsToMany.php b/src/Fields/BelongsToMany.php index a87749eb..89eee704 100644 --- a/src/Fields/BelongsToMany.php +++ b/src/Fields/BelongsToMany.php @@ -44,10 +44,8 @@ public function getRelation(Model $model): EloquentRelation */ protected function resolveField(Request $request, Field $field): void { - if (! is_null($this->apiUri)) { - $field->setApiUri(sprintf('%s/%s', $this->apiUri, $field->getUriKey())); - } - + $field->setAttribute('form', $this->getAttribute('form')); + $field->resolveErrorsUsing($this->errorsResolver); $field->setModelAttribute( sprintf('%s.*.%s', $this->getModelAttribute(), $field->getModelAttribute()) ); diff --git a/src/Fields/Editor.php b/src/Fields/Editor.php index 8ffc5cc6..3b172904 100644 --- a/src/Fields/Editor.php +++ b/src/Fields/Editor.php @@ -4,14 +4,19 @@ use Closure; use Cone\Root\Models\Medium; +use Cone\Root\Traits\RegistersRoutes; use Cone\Root\Traits\ResolvesFields; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Http\Request; +use Illuminate\Routing\Router; use Illuminate\Support\Facades\Config; class Editor extends Field { + use RegistersRoutes { + RegistersRoutes::registerRoutes as __registerRoutes; + } use ResolvesFields; /** @@ -41,17 +46,11 @@ public function __construct(string $label, string $modelAttribute = null) } /** - * {@inheritdoc} + * Get the URI key. */ - public function setApiUri(string $apiUri): static + public function getUriKey(): string { - if (! is_null($this->media)) { - $this->media->setApiUri( - sprintf('%s/%s', $apiUri, $this->media->getUriKey()) - ); - } - - return parent::setApiUri($apiUri); + return str_replace('.', '-', $this->getRequestKey()); } /** @@ -142,6 +141,20 @@ public function __construct(string $modelAttribute) }; } + /** + * Register the routes using the given router. + */ + public function registerRoutes(Request $request, Router $router): void + { + $this->__registerRoutes($request, $router); + + if (! is_null($this->media)) { + $router->prefix($this->getUriKey())->group(function (Router $router) use ($request): void { + $this->media->registerRoutes($request, $router); + }); + } + } + /** * {@inheritdoc} */ diff --git a/src/Fields/Field.php b/src/Fields/Field.php index 18ce8f0a..25969e6a 100644 --- a/src/Fields/Field.php +++ b/src/Fields/Field.php @@ -9,7 +9,6 @@ use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\MessageBag; use Illuminate\Database\Eloquent\Model; -use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Str; @@ -83,11 +82,6 @@ abstract class Field implements Arrayable, JsonSerializable */ protected bool $withOldValue = true; - /** - * The API URI. - */ - protected ?string $apiUri = null; - /** * Indicates if the field has been hydrated. */ @@ -148,40 +142,6 @@ public function getValidationKey(): string return $this->getRequestKey(); } - /** - * Get the URI key. - */ - public function getUriKey(): string - { - return str_replace('.', '-', $this->getRequestKey()); - } - - /** - * Set the API URI. - */ - public function setApiUri(string $apiUri): static - { - $this->apiUri = $apiUri; - - return $this; - } - - /** - * Get the API URI. - */ - public function getApiUri(): ?string - { - return $this->apiUri; - } - - /** - * Handle the incoming API request. - */ - public function handleApiRequest(Request $request, Model $model): JsonResponse - { - return new JsonResponse($this->toArray()); - } - /** * Set the label attribute. */ diff --git a/src/Fields/Fields.php b/src/Fields/Fields.php index 17bf7312..f456d335 100644 --- a/src/Fields/Fields.php +++ b/src/Fields/Fields.php @@ -2,8 +2,10 @@ namespace Cone\Root\Fields; +use Cone\Root\Traits\RegistersRoutes; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; +use Illuminate\Routing\Router; use Illuminate\Support\Arr; use Illuminate\Support\Collection; @@ -50,4 +52,18 @@ public function mapToFormComponents(Request $request, Model $model): array { return $this->map->toFormComponent($request, $model)->all(); } + + /** + * Register the field routes. + */ + public function registerRoutes(Request $request, Router $router): void + { + $router->prefix('fields')->group(function (Router $router) use ($request): void { + $this->each(static function (Field $field) use ($request, $router): void { + if (in_array(RegistersRoutes::class, class_uses_recursive($field))) { + $field->registerRoutes($request, $router); + } + }); + }); + } } diff --git a/src/Fields/Fieldset.php b/src/Fields/Fieldset.php index 59b105f5..1a386a9b 100644 --- a/src/Fields/Fieldset.php +++ b/src/Fields/Fieldset.php @@ -2,13 +2,16 @@ namespace Cone\Root\Fields; +use Cone\Root\Traits\RegistersRoutes; use Cone\Root\Traits\ResolvesFields; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; +use Illuminate\Routing\Router; use Illuminate\Support\Facades\App; class Fieldset extends Field { + use RegistersRoutes; use ResolvesFields; /** @@ -16,14 +19,31 @@ class Fieldset extends Field */ protected string $template = 'root::fields.fieldset'; + /** + * Get the URI key. + */ + public function getUriKey(): string + { + return str_replace('.', '-', $this->getRequestKey()); + } + /** * Handle the callback for the field resolution. */ protected function resolveField(Request $request, Field $field): void { - if (! is_null($this->apiUri)) { - $field->setApiUri(sprintf('%s/%s', $this->apiUri, $field->getUriKey())); - } + $field->setAttribute('form', $this->getAttribute('form')); + $field->resolveErrorsUsing($this->errorsResolver); + } + + /** + * Register the routes using the given router. + */ + public function registerRoutes(Request $request, Router $router): void + { + $router->prefix($this->getUriKey())->group(function (Router $router) use ($request): void { + $this->resolveFields($request)->registerRoutes($request, $router); + }); } /** @@ -77,7 +97,7 @@ public function toValidate(Request $request, Model $model): array { return array_merge( parent::toValidate($request, $model), - $this->resolveFields($request)->mapToValidate($request) + $this->resolveFields($request)->mapToValidate($request, $model) ); } } diff --git a/src/Fields/Media.php b/src/Fields/Media.php index 7162fddb..29548442 100644 --- a/src/Fields/Media.php +++ b/src/Fields/Media.php @@ -2,12 +2,15 @@ namespace Cone\Root\Fields; +use Cone\Root\Http\Controllers\MediaController; use Cone\Root\Models\Medium; use Cone\Root\Traits\HasMedia; +use Cone\Root\Traits\RegistersRoutes; use Illuminate\Database\Eloquent\Model; -use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\UploadedFile; +use Illuminate\Routing\Router; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\View; @@ -15,10 +18,9 @@ class Media extends File { - /** - * Indicates if the component is async. - */ - protected bool $async = true; + use RegistersRoutes { + RegistersRoutes::registerRoutes as __registerRoutes; + } /** * Indicates if the component is multiple. @@ -30,6 +32,22 @@ class Media extends File */ protected string $template = 'root::fields.media'; + /** + * Get the URI key. + */ + public function getUriKey(): string + { + return str_replace('.', '-', $this->getRequestKey()); + } + + /** + * Get the route parameter name. + */ + public function getRouteParameterName(): string + { + return 'field'; + } + /** * Get the modal key. */ @@ -68,13 +86,11 @@ public function paginate(Request $request, Model $model): array ->latest() ->paginate($request->input('per_page')) ->withQueryString() - ->setPath($this->apiUri) + ->setPath($this->getUri()) ->through(function (Medium $related) use ($request, $model): array { $option = $this->toOption($request, $model, $related); - $option['fields'] = array_map(static function (Field $field) use ($request, $model): array { - return $field->toFormComponent($request, $model); - }, $option['fields']); + $option['fields'] = $option['fields']->mapToFormComponents($request, $model); return array_merge($option, [ 'html' => View::make('root::fields.file-option', $option)->render(), @@ -137,6 +153,38 @@ public function store(Request $request, Model $model, UploadedFile $file): array return $this->stored($request, $model, $disk->path($file->getClientOriginalName())); } + /** + * Build the URI for the given request and model. + */ + public function buildUri(Request $request, Model $model): ?string + { + $uri = sprintf('%s?%s', $this->getUri(), Arr::query(array_filter([ + 'model' => $model->getKey(), + ]))); + + return rtrim($uri, '?'); + } + + /** + * Register the routes using the given router. + */ + public function registerRoutes(Request $request, Router $router): void + { + $this->__registerRoutes($request, $router); + + $router->prefix($this->getUriKey())->group(function (Router $router) use ($request): void { + $this->resolveFields($request)->registerRoutes($request, $router); + }); + } + + /** + * The routes that should be registered. + */ + public function routes(Router $router): void + { + $router->match(['GET', 'POST', 'DELETE'], '/', MediaController::class); + } + /** * {@inheritdoc} */ @@ -158,19 +206,7 @@ public function toFormComponent(Request $request, Model $model): array 'html' => View::make('root::fields.file-option', $option)->render(), ]); }, $data['options'] ?? []), + 'url' => $this->getUri() ? $this->buildUri($request, $model) : null, ]); } - - /** - * {@inheritdoc} - */ - public function handleApiRequest(Request $request, Model $model): JsonResponse - { - return match ($request->method()) { - 'GET' => new JsonResponse($this->paginate($request, $model)), - 'POST' => new JsonResponse($this->upload($request, $model), JsonResponse::HTTP_CREATED), - 'DELETE' => new JsonResponse(['deleted' => $this->prune($request, $model, $request->input('ids', []))]), - default => parent::handleApiRequest($request, $model), - }; - } } diff --git a/src/Fields/Relation.php b/src/Fields/Relation.php index 3bea0069..5f88b758 100644 --- a/src/Fields/Relation.php +++ b/src/Fields/Relation.php @@ -22,11 +22,6 @@ abstract class Relation extends Field */ protected bool $nullable = false; - /** - * Indicates if the component is async. - */ - protected bool $async = false; - /** * The Blade template. */ @@ -146,26 +141,6 @@ public function resolveDisplay(Model $related): mixed return call_user_func_array($this->displayResolver, [$related]); } - /** - * Set the async attribute. - */ - public function async(bool $value = true): static - { - $this->async = $value; - - // $this->template = $value ? 'root::fields.dropdown' : 'root::fields.select'; - - return $this; - } - - /** - * Determine if the field is asnyc. - */ - public function isAsync(): bool - { - return $this->async; - } - /** * {@inheritdoc} */ @@ -251,20 +226,6 @@ public function newOption(Model $related, string $label): Option return new Option($related->getKey(), $label); } - /** - * Build the API URI. - */ - protected function buildApiUri(Model $model): ?string - { - if (is_null($this->apiUri)) { - return $this->apiUri; - } - - return sprintf('%s?%s', $this->apiUri, http_build_query([ - 'model' => $model->getKey(), - ])); - } - /** * Get the option representation of the model and the related model. */ @@ -284,10 +245,8 @@ public function toOption(Request $request, Model $model, Model $related): array public function toFormComponent(Request $request, Model $model): array { return array_merge(parent::toFormComponent($request, $model), [ - 'async' => $this->isAsync(), 'nullable' => $this->isNullable(), - 'options' => $this->isAsync() ? [] : $this->resolveOptions($request, $model), - 'url' => $this->isAsync() ? $this->buildApiUri($model) : null, + 'options' => $this->resolveOptions($request, $model), ]); } } diff --git a/src/Fields/Repeater.php b/src/Fields/Repeater.php index e0ea2fc5..e40119a3 100644 --- a/src/Fields/Repeater.php +++ b/src/Fields/Repeater.php @@ -3,15 +3,20 @@ namespace Cone\Root\Fields; use Closure; +use Cone\Root\Http\Controllers\RepeaterController; +use Cone\Root\Traits\RegistersRoutes; use Cone\Root\Traits\ResolvesFields; use Illuminate\Database\Eloquent\Model; -use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Routing\Router; use Illuminate\Support\Facades\View; use Illuminate\Support\Str; class Repeater extends Field { + use RegistersRoutes { + RegistersRoutes::registerRoutes as __registerRoutes; + } use ResolvesFields { ResolvesFields::withFields as __withFields; } @@ -31,6 +36,22 @@ class Repeater extends Field */ protected ?int $max = null; + /** + * Get the URI key. + */ + public function getUriKey(): string + { + return str_replace('.', '-', $this->getRequestKey()); + } + + /** + * Get the route parameter name. + */ + public function getRouteParameterName(): string + { + return 'field'; + } + /** * Set the maximum number of options. */ @@ -78,10 +99,6 @@ public function getOldValue(Request $request): mixed */ protected function resolveField(Request $request, Field $field): void { - if (! is_null($this->apiUri)) { - $field->setApiUri(sprintf('%s/%s', $this->apiUri, $field->getUriKey())); - } - $field->setModelAttribute( sprintf('%s.*.%s', $this->getModelAttribute(), $field->getModelAttribute()) ); @@ -163,6 +180,26 @@ public function buildOption(Request $request, Model $model): array return $option; } + /** + * Register the routes using the given router. + */ + public function registerRoutes(Request $request, Router $router): void + { + $this->__registerRoutes($request, $router); + + $router->prefix($this->getUriKey())->group(function (Router $router) use ($request): void { + $this->resolveFields($request)->registerRoutes($request, $router); + }); + } + + /** + * The routes that should be registered. + */ + public function routes(Router $router): void + { + $router->post('/', RepeaterController::class); + } + /** * Get the option representation of the model and the temporary model. */ @@ -193,7 +230,7 @@ public function toFormComponent(Request $request, Model $model): array 'html' => View::make('root::fields.repeater-option', $option)->render(), ]); }, $this->resolveOptions($request, $model)), - 'url' => $this->getApiUri(), + 'url' => $this->getUri(), ]); } @@ -207,15 +244,4 @@ public function toValidate(Request $request, Model $model): array $this->resolveFields($request)->mapToValidate($request, $model) ); } - - /** - * {@inheritdoc} - */ - public function handleApiRequest(Request $request, Model $model): JsonResponse - { - return match ($request->method()) { - 'POST' => new JsonResponse($this->buildOption($request, $model)), - default => parent::handleApiRequest($request, $model), - }; - } } diff --git a/src/Http/Controllers/ActionController.php b/src/Http/Controllers/ActionController.php index 6d0c4c6d..2fbc94fd 100644 --- a/src/Http/Controllers/ActionController.php +++ b/src/Http/Controllers/ActionController.php @@ -13,7 +13,7 @@ class ActionController extends Controller */ public function __invoke(Request $request): RedirectResponse { - $action = $request->route('rootAction'); + $action = $request->route('action'); Gate::allowIf($action->authorized($request)); diff --git a/src/Http/Controllers/MediaController.php b/src/Http/Controllers/MediaController.php new file mode 100644 index 00000000..29e3b8ca --- /dev/null +++ b/src/Http/Controllers/MediaController.php @@ -0,0 +1,32 @@ +route('resource'); + + $field = $request->route('field'); + + // Gate::allowIf($field->authorized($request, $model)); + + $model = $request->filled('model') + ? $resource->resolveRouteBinding($request, $request->input('model')) + : $resource->getModelInstance(); + + return match ($request->method()) { + 'GET' => new JsonResponse($field->paginate($request, $model)), + 'POST' => new JsonResponse($field->upload($request, $model), JsonResponse::HTTP_CREATED), + 'DELETE' => new JsonResponse(['deleted' => $field->prune($request, $model, $request->input('ids', []))]), + }; + } +} diff --git a/src/Http/Controllers/RepeaterController.php b/src/Http/Controllers/RepeaterController.php new file mode 100644 index 00000000..80a5be59 --- /dev/null +++ b/src/Http/Controllers/RepeaterController.php @@ -0,0 +1,27 @@ +route('resource'); + + $field = $request->route('field'); + + // Gate::allowIf($field->authorized($request, $model)); + + $model = $request->filled('model') + ? $resource->resolveRouteBinding($request, $request->input('model')) + : $resource->getModelInstance(); + + return new JsonResponse($field->buildOption($request, $model)); + } +} diff --git a/src/Http/Controllers/ResourceController.php b/src/Http/Controllers/ResourceController.php index cd7e460e..1efc5ab0 100644 --- a/src/Http/Controllers/ResourceController.php +++ b/src/Http/Controllers/ResourceController.php @@ -3,7 +3,6 @@ namespace Cone\Root\Http\Controllers; use Cone\Root\Http\Middleware\AuthorizeResource; -use Cone\Root\Resources\Resource; use Cone\Root\Support\Alert; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; @@ -26,8 +25,10 @@ public function __construct() /** * Display a listing of the resource. */ - public function index(Request $request, Resource $resource): Response + public function index(Request $request): Response { + $resource = $request->route('resource'); + if ($resource->getPolicy()) { $this->authorize('viewAny', $resource->getModel()); } @@ -41,8 +42,10 @@ public function index(Request $request, Resource $resource): Response /** * Show the form for creating a new resource. */ - public function create(Request $request, Resource $resource): Response + public function create(Request $request): Response { + $resource = $request->route('resource'); + if ($resource->getPolicy()) { $this->authorize('create', $resource->getModel()); } @@ -56,8 +59,10 @@ public function create(Request $request, Resource $resource): Response /** * Store a newly created resource in storage. */ - public function store(Request $request, Resource $resource): RedirectResponse + public function store(Request $request): RedirectResponse { + $resource = $request->route('resource'); + if ($resource->getPolicy()) { $this->authorize('create', $resource->getModel()); } @@ -73,8 +78,10 @@ public function store(Request $request, Resource $resource): RedirectResponse /** * Show the form for editing the specified resource. */ - public function edit(Request $request, Resource $resource, Model $model): Response + public function edit(Request $request, Model $model): Response { + $resource = $request->route('resource'); + if ($resource->getPolicy()) { $this->authorize('update', $model); } @@ -88,8 +95,10 @@ public function edit(Request $request, Resource $resource, Model $model): Respon /** * Update the specified resource in storage. */ - public function update(Request $request, Resource $resource, Model $model): RedirectResponse + public function update(Request $request, Model $model): RedirectResponse { + $resource = $request->route('resource'); + if ($resource->getPolicy()) { $this->authorize('update', $model); } @@ -103,8 +112,10 @@ public function update(Request $request, Resource $resource, Model $model): Redi /** * Remove the specified resource from storage. */ - public function destroy(Request $request, Resource $resource, Model $model): RedirectResponse + public function destroy(Request $request, Model $model): RedirectResponse { + $resource = $request->route('resource'); + $trashed = in_array(SoftDeletes::class, class_uses_recursive($model)) && $model->trashed(); if ($resource->getPolicy()) { @@ -120,8 +131,10 @@ public function destroy(Request $request, Resource $resource, Model $model): Red /** * Restore the specified resource in storage. */ - public function restore(Request $request, Resource $resource, Model $model): RedirectResponse + public function restore(Request $request, Model $model): RedirectResponse { + $resource = $request->route('resource'); + if ($resource->getPolicy()) { $this->authorize('restore', $model); } diff --git a/src/Http/Controllers/ResourceFieldController.php b/src/Http/Controllers/ResourceFieldController.php deleted file mode 100644 index 5901726a..00000000 --- a/src/Http/Controllers/ResourceFieldController.php +++ /dev/null @@ -1,31 +0,0 @@ -findField( - $request, $request->path() - ); - - if (is_null($field)) { - throw new NotFoundHttpException(); - } - - $model = $request->filled('model') - ? $resource->resolveRouteBinding($request, $request->input('model')) - : $resource->getModelInstance(); - - return $field->handleApiRequest($request, $model); - } -} diff --git a/src/Http/Middleware/HandleRootRequests.php b/src/Http/Middleware/HandleRootRequests.php index 51e0a2b2..0606da9d 100644 --- a/src/Http/Middleware/HandleRootRequests.php +++ b/src/Http/Middleware/HandleRootRequests.php @@ -3,7 +3,6 @@ namespace Cone\Root\Http\Middleware; use Closure; -use Cone\Root\Root; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; @@ -16,8 +15,6 @@ class HandleRootRequests */ public function handle(Request $request, Closure $next): Response { - Root::instance()->boot(); - return $next($request); } } diff --git a/src/Resources/Resource.php b/src/Resources/Resource.php index 7ee1358f..c71c78f5 100644 --- a/src/Resources/Resource.php +++ b/src/Resources/Resource.php @@ -7,8 +7,10 @@ use Cone\Root\Filters\Filter; use Cone\Root\Interfaces\Form; use Cone\Root\Interfaces\Table; +use Cone\Root\Root; use Cone\Root\Traits\AsForm; use Cone\Root\Traits\Authorizable; +use Cone\Root\Traits\RegistersRoutes; use Cone\Root\Traits\ResolvesActions; use Cone\Root\Traits\ResolvesColumns; use Cone\Root\Traits\ResolvesFilters; @@ -20,15 +22,18 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Http\Request; +use Illuminate\Routing\Router; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Gate; -use Illuminate\Support\Facades\URL; use Illuminate\Support\Str; -class Resource implements Arrayable, Form, Table +abstract class Resource implements Arrayable, Form, Table { use AsForm; use Authorizable; + use RegistersRoutes { + RegistersRoutes::registerRoutes as __registerRoutes; + } use ResolvesActions; use ResolvesColumns; use ResolvesFilters; @@ -54,6 +59,16 @@ class Resource implements Arrayable, Form, Table */ protected string $icon = 'archive'; + /** + * Boot the resource. + */ + public function boot(Root $root): void + { + $root->routes(function (Router $router) use ($root): void { + $this->registerRoutes($root->app['request'], $router); + }); + } + /** * Get the model for the resource. */ @@ -201,20 +216,12 @@ public function isSoftDeletable(): bool return in_array(SoftDeletes::class, class_uses_recursive($this->getModel())); } - /** - * Get the resource URL. - */ - public function getUrl(): string - { - return URL::route('root.resource.index', $this->getUriKey()); - } - /** * Get the URL for the given model. */ public function modelUrl(Model $model): string { - return URL::route($model->exists ? 'root.resource.update' : 'root.resource.store', [$this->getUriKey(), $model]); + return sprintf('%s/%s', $this->getUri(), $model->exists ? $model->getRouteKey() : ''); } /** @@ -222,11 +229,8 @@ public function modelUrl(Model $model): string */ protected function resolveField(Request $request, Field $field): void { - $field->setApiUri(sprintf('/root/api/%s/fields/%s', $this->getKey(), $field->getUriKey())); $field->setAttribute('form', $this->getKey()); - $field->resolveErrorsUsing(function (Request $request): MessageBag { - return $this->errors($request); - }); + $field->resolveErrorsUsing(fn (Request $request): MessageBag => $this->errors($request)); } /** @@ -235,7 +239,6 @@ protected function resolveField(Request $request, Field $field): void protected function resolveAction(Request $request, Action $action): void { $action->setQuery($this->resolveFilteredQuery($request)); - $action->setApiUri(sprintf('/root/api/%s/actions/%s', $this->getKey(), $action->getUriKey())); } /** @@ -268,6 +271,19 @@ public function paginate(Request $request): LengthAwarePaginator }); } + /** + * Register the routes. + */ + public function registerRoutes(Request $request, Router $router): void + { + $this->__registerRoutes($request, $router); + + $router->prefix($this->getUriKey())->group(function (Router $router) use ($request): void { + $this->resolveActions($request)->registerRoutes($request, $router); + $this->resolveFields($request)->registerRoutes($request, $router); + }); + } + /** * Get the instance as an array. */ @@ -280,7 +296,7 @@ public function toArray(): array 'modelName' => $this->getModelName(), 'name' => $this->getName(), 'uriKey' => $this->getUriKey(), - 'url' => $this->getUrl(), + 'url' => $this->getUri(), ]; } @@ -314,7 +330,7 @@ public function toCreate(Request $request): array return array_merge($this->toArray(), [ 'title' => __('Create :model', ['model' => $this->getModelName()]), 'model' => $model = $this->getModelInstance(), - 'action' => $this->getUrl(), + 'action' => $this->getUri(), 'method' => 'POST', 'fields' => $this->resolveFields($request)->mapToFormComponents($request, $model), ]); diff --git a/src/Root.php b/src/Root.php index b974a0a9..437e5c8e 100644 --- a/src/Root.php +++ b/src/Root.php @@ -3,7 +3,6 @@ namespace Cone\Root; use Closure; -use Cone\Root\Resources\Resource; use Cone\Root\Resources\Resources; use Cone\Root\Widgets\Widgets; use Illuminate\Contracts\Foundation\Application; @@ -65,6 +64,8 @@ public static function instance(): static */ public function boot(): void { + $this->resources->each->boot($this); + foreach ($this->booting as $callback) { call_user_func_array($callback, [$this]); } @@ -120,12 +121,4 @@ public function getDomain(): string { return (string) Config::get('root.domain', null); } - - /** - * Get the current resource. - */ - public function getCurrentResource(): ?Resource - { - return $this->resources->current($this->app['request']); - } } diff --git a/src/RootServiceProvider.php b/src/RootServiceProvider.php index 41d3427d..8de09cf6 100644 --- a/src/RootServiceProvider.php +++ b/src/RootServiceProvider.php @@ -53,6 +53,10 @@ public function register(): void $this->app->afterResolving(EncryptCookies::class, static function (EncryptCookies $middleware): void { $middleware->disableFor('__root_theme'); }); + + $this->app->booted(static function (Application $app): void { + $app->make(Root::class)->boot(); + }); } /** @@ -113,10 +117,6 @@ protected function registerRoutes(): void $root = $this->app->make(Root::class); - $this->app['router']->bind('resource', static function (string $key) use ($root): Resource { - return $root->resources->resolve($key); - }); - $this->app['router']->bind('resourceModel', function (string $id, Route $route): Model { return $route->parameter('resource')->resolveRouteBinding($this->app['request'], $id); }); @@ -187,7 +187,7 @@ protected function registerNavigation(): void $this->app->make(Root::class)->booting(static function (Root $root): void { $root->resources->each(static function (Resource $resource): void { Nav::location('sidebar')->new( - $resource->getUrl(), + $resource->getUri(), $resource->getName(), ['icon' => $resource->getIcon()] ); diff --git a/src/Traits/RegistersRoutes.php b/src/Traits/RegistersRoutes.php new file mode 100644 index 00000000..f06f9b50 --- /dev/null +++ b/src/Traits/RegistersRoutes.php @@ -0,0 +1,74 @@ +uri; + } + + /** + * Get the route parameter name. + */ + public function getRouteParameterName(): string + { + return Str::of(self::class)->classBasename()->lower()->value(); + } + + /** + * Register the routes using the given router. + */ + public function registerRoutes(Request $request, Router $router): void + { + $this->uri = Str::start(sprintf('%s/%s', $router->getLastGroupPrefix(), $this->getUriKey()), '/'); + + if (! App::routesAreCached()) { + $router->prefix($this->getUriKey())->group(function (Router $router): void { + $this->routes($router); + }); + } + + $router->matched(function (RouteMatched $event): void { + if (str_starts_with(Str::start($event->request->path(), '/'), $this->getUri())) { + $this->routeMatched($event); + } + }); + } + + /** + * Handle the route matched event. + */ + public function routeMatched(RouteMatched $event): void + { + $event->route->setParameter($this->getRouteParameterName(), $this); + } + + /** + * The routes that should be registered. + */ + public function routes(Router $router): void + { + // + } +} diff --git a/src/Traits/ResolvesFields.php b/src/Traits/ResolvesFields.php index 7a42d559..d1fb8fd8 100644 --- a/src/Traits/ResolvesFields.php +++ b/src/Traits/ResolvesFields.php @@ -67,23 +67,4 @@ protected function resolveField(Request $request, Field $field): void { // } - - /** - * Find the field with the given API URI. - */ - public function findField(Request $request, string $apiUri): ?Field - { - foreach ($this->resolveFields($request)->all() as $field) { - if (trim($field->getApiUri(), '/') === trim($apiUri, '/')) { - return $field; - } - - if (in_array(ResolvesFields::class, class_uses_recursive($field)) - && ! is_null($subfield = $field->findField($request, $apiUri))) { - return $subfield; - } - } - - return null; - } }