diff --git a/src/BackpackServiceProvider.php b/src/BackpackServiceProvider.php index c3119fc4a6..816a044e77 100644 --- a/src/BackpackServiceProvider.php +++ b/src/BackpackServiceProvider.php @@ -91,6 +91,10 @@ public function register() return new DatabaseSchema(); }); + $this->app->scoped('BackpackLifecycleHooks', function ($app) { + return new app\Library\CrudPanel\Hooks\LifecycleHooks(); + }); + $this->app->singleton('BackpackViewNamespaces', function ($app) { return new ViewNamespaces(); }); diff --git a/src/app/Http/Controllers/CrudController.php b/src/app/Http/Controllers/CrudController.php index cc929b75ac..180c6f7837 100644 --- a/src/app/Http/Controllers/CrudController.php +++ b/src/app/Http/Controllers/CrudController.php @@ -3,6 +3,7 @@ namespace Backpack\CRUD\app\Http\Controllers; use Backpack\CRUD\app\Library\Attributes\DeprecatedIgnoreOnRuntime; +use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Routing\Controller; @@ -19,6 +20,7 @@ class CrudController extends Controller use DispatchesJobs, ValidatesRequests; public $crud; + public $data = []; public function __construct() @@ -40,8 +42,14 @@ public function __construct() $this->crud->setRequest($request); + LifecycleHook::trigger('crud:before_setup_defaults', [$this]); $this->setupDefaults(); + LifecycleHook::trigger('crud:after_setup_defaults', [$this]); + + LifecycleHook::trigger('crud:before_setup', [$this]); $this->setup(); + LifecycleHook::trigger('crud:after_setup', [$this]); + $this->setupConfigurationForCurrentOperation(); return $next($request); @@ -109,13 +117,15 @@ protected function setupConfigurationForCurrentOperation() /* * FIRST, run all Operation Closures for this operation. * - * It's preferred for this to closures first, because + * It's preferred for this to run closures first, because * (1) setup() is usually higher in a controller than any other method, so it's more intuitive, * since the first thing you write is the first thing that is being run; * (2) operations use operation closures themselves, inside their setupXxxDefaults(), and * you'd like the defaults to be applied before anything you write. That way, anything you * write is done after the default, so you can remove default settings, etc; */ + LifecycleHook::trigger($operationName.':before_setup', [$this]); + $this->crud->applyConfigurationFromSettings($operationName); /* @@ -124,5 +134,7 @@ protected function setupConfigurationForCurrentOperation() if (method_exists($this, $setupClassName)) { $this->{$setupClassName}(); } + + LifecycleHook::trigger($operationName.':after_setup', [$this]); } } diff --git a/src/app/Http/Controllers/Operations/Concerns/HasForm.php b/src/app/Http/Controllers/Operations/Concerns/HasForm.php index 1e38e83dba..10a202a963 100644 --- a/src/app/Http/Controllers/Operations/Concerns/HasForm.php +++ b/src/app/Http/Controllers/Operations/Concerns/HasForm.php @@ -2,6 +2,7 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations\Concerns; +use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; use Illuminate\Support\Facades\Route; use Illuminate\Support\Str; @@ -38,8 +39,7 @@ protected function formDefaults(string $operationName, string $buttonStack = 'li // Access $this->crud->allowAccess($operationName); - // Config - $this->crud->operation($operationName, function () use ($operationName) { + LifecycleHook::hookInto($operationName.':before_setup', function () use ($operationName) { // if the backpack.operations.{operationName} config exists, use that one // otherwise, use the generic backpack.operations.form config if (config()->has('backpack.operations.'.$operationName)) { @@ -61,8 +61,7 @@ protected function formDefaults(string $operationName, string $buttonStack = 'li ]); }); - // Default Button - $this->crud->operation(['list', 'show'], function () use ($operationName, $buttonStack, $buttonMeta) { + LifecycleHook::hookInto(['list:before_setup', 'show:before_setup'], function () use ($operationName, $buttonStack, $buttonMeta) { $this->crud->button($operationName)->view('crud::buttons.quick')->stack($buttonStack)->meta($buttonMeta); }); } diff --git a/src/app/Http/Controllers/Operations/CreateOperation.php b/src/app/Http/Controllers/Operations/CreateOperation.php index 58ca954733..e6282ff68f 100644 --- a/src/app/Http/Controllers/Operations/CreateOperation.php +++ b/src/app/Http/Controllers/Operations/CreateOperation.php @@ -2,6 +2,7 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; use Illuminate\Support\Facades\Route; trait CreateOperation @@ -35,12 +36,11 @@ protected function setupCreateDefaults() { $this->crud->allowAccess('create'); - $this->crud->operation('create', function () { - $this->crud->loadDefaultOperationSettingsFromConfig(); + LifecycleHook::hookInto('create:before_setup', function () { $this->crud->setupDefaultSaveActions(); }); - $this->crud->operation('list', function () { + LifecycleHook::hookInto('list:before_setup', function () { $this->crud->addButton('top', 'create', 'view', 'crud::buttons.create'); }); } diff --git a/src/app/Http/Controllers/Operations/DeleteOperation.php b/src/app/Http/Controllers/Operations/DeleteOperation.php index 81a80199fa..8862227394 100644 --- a/src/app/Http/Controllers/Operations/DeleteOperation.php +++ b/src/app/Http/Controllers/Operations/DeleteOperation.php @@ -2,6 +2,7 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; use Illuminate\Support\Facades\Route; trait DeleteOperation @@ -29,11 +30,11 @@ protected function setupDeleteDefaults() { $this->crud->allowAccess('delete'); - $this->crud->operation('delete', function () { + LifecycleHook::hookInto('delete:before_setup', function () { $this->crud->loadDefaultOperationSettingsFromConfig(); }); - $this->crud->operation(['list', 'show'], function () { + LifecycleHook::hookInto(['list:before_setup', 'show:before_setup'], function () { $this->crud->addButton('line', 'delete', 'view', 'crud::buttons.delete', 'end'); }); } diff --git a/src/app/Http/Controllers/Operations/ListOperation.php b/src/app/Http/Controllers/Operations/ListOperation.php index c6eb66c7f5..a7698d18a0 100644 --- a/src/app/Http/Controllers/Operations/ListOperation.php +++ b/src/app/Http/Controllers/Operations/ListOperation.php @@ -2,6 +2,7 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; use Illuminate\Support\Facades\Route; trait ListOperation @@ -43,7 +44,7 @@ protected function setupListDefaults() { $this->crud->allowAccess('list'); - $this->crud->operation('list', function () { + LifecycleHook::hookInto('list:before_setup', function () { $this->crud->loadDefaultOperationSettingsFromConfig(); $this->crud->setOperationSetting('datatablesUrl', $this->crud->getRoute()); }); diff --git a/src/app/Http/Controllers/Operations/ReorderOperation.php b/src/app/Http/Controllers/Operations/ReorderOperation.php index 0fe547f78a..90fcee0efd 100644 --- a/src/app/Http/Controllers/Operations/ReorderOperation.php +++ b/src/app/Http/Controllers/Operations/ReorderOperation.php @@ -2,6 +2,7 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; use Illuminate\Support\Facades\Route; trait ReorderOperation @@ -36,7 +37,7 @@ protected function setupReorderDefaults() $this->crud->set('reorder.enabled', true); $this->crud->allowAccess('reorder'); - $this->crud->operation('reorder', function () { + LifecycleHook::hookInto('reorder:before_setup', function () { $this->crud->loadDefaultOperationSettingsFromConfig(); $this->crud->setOperationSetting('reorderColumnNames', [ 'parent_id' => 'parent_id', @@ -46,7 +47,7 @@ protected function setupReorderDefaults() ]); }); - $this->crud->operation('list', function () { + LifecycleHook::hookInto('list:before_setup', function () { $this->crud->addButton('top', 'reorder', 'view', 'crud::buttons.reorder'); }); } diff --git a/src/app/Http/Controllers/Operations/ShowOperation.php b/src/app/Http/Controllers/Operations/ShowOperation.php index d2eedc72d8..e1ea3ab53b 100644 --- a/src/app/Http/Controllers/Operations/ShowOperation.php +++ b/src/app/Http/Controllers/Operations/ShowOperation.php @@ -2,6 +2,7 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; use Illuminate\Support\Facades\Route; trait ShowOperation @@ -30,7 +31,7 @@ protected function setupShowDefaults() $this->crud->allowAccess('show'); $this->crud->setOperationSetting('setFromDb', true); - $this->crud->operation('show', function () { + LifecycleHook::hookInto('show:before_setup', function () { $this->crud->loadDefaultOperationSettingsFromConfig(); if (! method_exists($this, 'setupShowOperation')) { @@ -38,11 +39,11 @@ protected function setupShowDefaults() } }); - $this->crud->operation('list', function () { + LifecycleHook::hookInto(['list:before_setup'], function () { $this->crud->addButton('line', 'show', 'view', 'crud::buttons.show', 'beginning'); }); - $this->crud->operation(['create', 'update'], function () { + LifecycleHook::hookInto(['create:before_setup', 'update:before_setup'], function () { $this->crud->addSaveAction([ 'name' => 'save_and_preview', 'visible' => function ($crud) { diff --git a/src/app/Http/Controllers/Operations/UpdateOperation.php b/src/app/Http/Controllers/Operations/UpdateOperation.php index f3c622a9c5..f0c656092c 100644 --- a/src/app/Http/Controllers/Operations/UpdateOperation.php +++ b/src/app/Http/Controllers/Operations/UpdateOperation.php @@ -2,6 +2,7 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; +use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; use Illuminate\Support\Facades\Route; trait UpdateOperation @@ -35,7 +36,7 @@ protected function setupUpdateDefaults() { $this->crud->allowAccess('update'); - $this->crud->operation('update', function () { + LifecycleHook::hookInto('update:before_setup', function () { $this->crud->loadDefaultOperationSettingsFromConfig(); if ($this->crud->getModel()->translationEnabled()) { @@ -49,7 +50,7 @@ protected function setupUpdateDefaults() $this->crud->setupDefaultSaveActions(); }); - $this->crud->operation(['list', 'show'], function () { + LifecycleHook::hookInto(['list:before_setup', 'show:before_setup'], function () { $this->crud->addButton('line', 'update', 'view', 'crud::buttons.update', 'end'); }); } diff --git a/src/app/Library/CrudPanel/CrudRouter.php b/src/app/Library/CrudPanel/CrudRouter.php index 0c1c1f464c..fd52ee5cd8 100644 --- a/src/app/Library/CrudPanel/CrudRouter.php +++ b/src/app/Library/CrudPanel/CrudRouter.php @@ -2,6 +2,7 @@ namespace Backpack\CRUD\app\Library\CrudPanel; +use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; use Illuminate\Support\Facades\App; use ReflectionClass; @@ -18,7 +19,9 @@ public static function setupControllerRoutes(string $name, string $routeName, st if (empty($setupRoutesMethod->getAttributes(\Backpack\CRUD\app\Library\Attributes\DeprecatedIgnoreOnRuntime::class))) { // when the attribute is not found the developer has overwritten the method // we will keep the old behavior for backwards compatibility + LifecycleHook::trigger('crud:before_setup_routes', [$name, $routeName, $controller]); $setupRoutesMethod->invoke(App::make($namespacedController), $name, $routeName, $controller); + LifecycleHook::trigger('crud:after_setup_routes', [$name, $routeName, $controller]); return; } @@ -32,7 +35,9 @@ public static function setupControllerRoutes(string $name, string $routeName, st str_ends_with($method->getName(), 'Routes') ) { $method->setAccessible(true); + LifecycleHook::trigger('crud:before_setup_routes', [$name, $routeName, $controller]); $method->invoke($controllerInstance, $name, $routeName, $controller); + LifecycleHook::trigger('crud:after_setup_routes', [$name, $routeName, $controller]); } } } diff --git a/src/app/Library/CrudPanel/Hooks/Facades/LifecycleHook.php b/src/app/Library/CrudPanel/Hooks/Facades/LifecycleHook.php new file mode 100644 index 0000000000..6401e95a2b --- /dev/null +++ b/src/app/Library/CrudPanel/Hooks/Facades/LifecycleHook.php @@ -0,0 +1,20 @@ +hooks[$hook][] = $callback; + } + } + + public function trigger(string|array $hooks, array $parameters = []): void + { + $hooks = is_array($hooks) ? $hooks : [$hooks]; + foreach ($hooks as $hook) { + if (isset($this->hooks[$hook])) { + foreach ($this->hooks[$hook] as $callback) { + if ($callback instanceof \Closure) { + $callback(...$parameters); + } + } + } + } + } + + public function has(string $hook): bool + { + return isset($this->hooks[$hook]); + } +} diff --git a/src/app/Library/CrudPanel/Traits/Operations.php b/src/app/Library/CrudPanel/Traits/Operations.php index 3a3c94c395..373b3ea0fe 100644 --- a/src/app/Library/CrudPanel/Traits/Operations.php +++ b/src/app/Library/CrudPanel/Traits/Operations.php @@ -2,6 +2,8 @@ namespace Backpack\CRUD\app\Library\CrudPanel\Traits; +use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; + trait Operations { /* @@ -59,30 +61,34 @@ public function setCurrentOperation($operation_name) * @param string|array $operation Operation name in string form * @param bool|\Closure $closure Code that calls CrudPanel methods. * @return void + * + * @deprecated use LifecycleHook::hookInto($operation.':before_setup', $closure) instead */ public function operation($operations, $closure = false) { - return $this->configureOperation($operations, $closure); + $operations = is_array($operations) ? $operations : [$operations]; + foreach ($operations as $operation) { + LifecycleHook::hookInto($operation.':before_setup', $closure); + } } /** * Store a closure which configures a certain operation inside settings. - * Allc configurations are put inside that operation's namespace. + * All configurations are put inside that operation's namespace. * Ex: show.configuration. * * @param string|array $operation Operation name in string form * @param bool|\Closure $closure Code that calls CrudPanel methods. * @return void + * + * @deprecated use LifecycleHook::hookInto($operation.':before_setup', $closure) instead */ public function configureOperation($operations, $closure = false) { - $operations = (array) $operations; + $operations = is_array($operations) ? $operations : [$operations]; foreach ($operations as $operation) { - $configuration = (array) $this->get($operation.'.configuration'); - $configuration[] = $closure; - - $this->set($operation.'.configuration', $configuration); + LifecycleHook::hookInto($operation.':before_setup', $closure); } } @@ -91,24 +97,15 @@ public function configureOperation($operations, $closure = false) * This is called when an operation does setCurrentOperation(). * * - * @param string|array $operations [description] + * @param string|array $operations * @return void */ public function applyConfigurationFromSettings($operations) { - $operations = (array) $operations; + $operations = is_array($operations) ? $operations : [$operations]; foreach ($operations as $operation) { - $configuration = (array) $this->get($operation.'.configuration'); - - if (count($configuration)) { - foreach ($configuration as $closure) { - if (is_callable($closure)) { - // apply the closure - ($closure)(); - } - } - } + LifecycleHook::trigger($operation.':before_setup'); } } }