CRUD icon indicating copy to clipboard operation
CRUD copied to clipboard

[POC] Operation hooks

Open pxpm opened this issue 1 year ago • 3 comments

The objective of this PR is to streamline the usage of "hooks" in our software, so that developer can do the most possible, without overwrites.

We have already concluded that overwrites are the pain point when updating backpack, and it also limits our options when we want to push new features. It's not uncommon to find conditionals in our codebase to account for overwritten files.

This is very very wip. I just got the liberty from @tabacitu to work on some concepts for stuff we aim to do, this is one of those.

pxpm avatar Oct 10 '24 11:10 pxpm

Playground for syntax:

<?php

        static::created(function (User $user) {
            // ...
        });

        OperationHook::register(
            OperationHooks::AFTER_OPERATION_SETUP, 
            'list', 
            function () { $this->runCustomViews(); }
        );

        // equivalent
        Backpack::hook(
            event: OperationHooks::AFTER_OPERATION_SETUP,
            operation: 'list',
            run: function() { $this->runCustomViews(); }
        );

        // equivalent
        Backpack::hook(
            event: 'afterOperationSetup',
            operation: 'list',
            run: function() { $this->runCustomViews(); }
        );

        // nah
        BackpackHook::afterOperationSetup('list', function() {
            $this->runCustomViews();
        });

        // nah
        BackpackHook::on(OperationHooks::AFTER_OPERATION_SETUP)
            ->operation('list')
            ->do(function() {
                $this->runCustomViews();
            });

        // -----
        BackpackHook::moderated(
            operation: 'moderate',
            run: function() { $this->runCustomViews(); }
        );

        'moderated' => [
            'operation' => 'moderate',
            'closure' => (function() {})
        ]

        // ModerateOperation
        public function saveModeration() {
            // do something
            $args = [
                $entry,
            ];
            BackpackHook::run('moderated', $args);
        }

        // -----
        
        BackpackHook::operation('list', function() {});
        BackpackHook::operation('list', function() {});

        Backpack::hookInto('moderated', function() {});
        Backpack::hookIntoOperation('list', 'moderated', function() {});
        

tabacitu avatar Oct 14 '24 14:10 tabacitu

Todo: docs for this PR (original PR, with separate PanelHooks and OperationHooks).

tabacitu avatar Oct 14 '24 14:10 tabacitu

OperationHooks

OperationHooks are a way to hook into the operation process. They are used to modify the operation process in some way, before or after the operation is setup. They are also used to modify the behavior of some setup settings, like setupOperationFromConfig.

There are three types of OperationHooks: BEFORE_OPERATION_SETUP, AFTER_OPERATION_SETUP and OPERATION_SETUP_CONFIGS.

BEFORE_OPERATION_SETUP

This hook is called before the operation is setup. It is used to modify the operation in some way, before it is setup.

```php
public function setup()
{
    OperationHooks::register(OperationHooks::BEFORE_OPERATION_SETUP, function($crudController) {
        // do something with the operation
    });
}
```

AFTER_OPERATION_SETUP

This hook is called after the operation is setup. It is used to modify the operation in some way, after it is setup.

```php
public function setup()
{
    OperationHooks::register(OperationHooks::AFTER_OPERATION_SETUP, function($crudController) {
        // do something with the operation
    });
}
```

OPERATION_SETUP_CONFIGS

This hook is used to modify the behavior of some setup settings, like from what file should the operation load his configs.

```php
public function setup()
{
    OperationHooks::register(OperationHooks::OPERATION_SETUP_CONFIGS, function($crudController) {
        return 'backpack.vendor.operation.someCustomFile';
    });
}
```

PanelHooks

PanelHooks can be seen a general purpose hooks, they are used to modify the panel, modify operations at different operation points etc. Backpack provides some default PanelHooks, but you can create your own PanelHooks.

BEFORE_SETUP_ROUTES AFTER_SETUP_ROUTES BEFORE_SETUP_DEFAULTS AFTER_SETUP_DEFAULTS BEFORE_CONTROLLER_SETUP AFTER_CONTROLLER_SETUP

If you are creating a custom operation, you can use the PanelHooks to create a point in your operation where developers using your operation can "hook" to modify the default behavior of your operation: eg:


// ModerateOperation.php

// .. setup routes, defaults etc ..

public function moderate($id) 
{
    // do some moderation functions ...

    PanelHooks::run('after-moderation', ['id' => $id, 'moderatedEntry' => $entry]);
}

// developers can then register they own behavior for the 'after-moderation' hook
// for example in a middleware, or in the `setup()` of the controller where they use your operation:

public function setup()
{
    PanelHooks::register('after-moderation', function($id, $moderatedEntry) {
        // do something with the moderated entry
    });
}

The main point of Hooks is to reduce the amount of overwrites, promoting a more modular and reusable code without the need to catter to all different ways someone might want to use your operation.

pxpm avatar Oct 17 '24 09:10 pxpm

CRUD Lifecycle Events

At important points in the CRUD Lifecycle, Backpack triggers what we call "lifecycle events". You can hook into those events - by registering custom code that will run when that lifecycle event happens. This allows you to customize the process, without having to override any of the core files for CRUD or an Operation.

For example, in a Backpack CRUD all routes are setup on the CrudController using methods like setupModerateOperationRoutes(). Before those methods are called, Backpack calls LifecycleEvent::trigger('crud:before_all_route_setup'). If you want to add your own code that runs there, you can do:

LifecycleEvent::hookInto('crud:before_all_route_setup', function($controller, $operation) {
    // do something useful
});

Here are all the Lifecycle Events we currently have:

  • crud:before_all_route_setup - before any operation routes are registered
  • crud:after_all_route_setup - after all operation routes have been registered
  • crud:before_current_operation_setup - before any operation is set up
  • crud:after_current_operation_setup - after that operation has been set up

In addition to the general lifecycle events above, each operation can trigger its own lifecycle events. For example, here are the lifecycle events triggered by the Create operation:

  • create:before_route_setup - exposes parameters: $crud, $segment, $routeName, $controller
  • create:after_route_setup - exposes parameters: $crud, $segment, $routeName, $controller
  • create:before_defaults - exposes parameters: $crud
  • create:after_defaults - exposes parameters: $crud
  • create:before_setup - exposes parameters: $crud
  • create:after_after - exposes parameters: $crud
  • create:before_create_logic - exposes parameters: $crud
  • create:after_create_logic - exposes parameters: $crud
  • create:before_store_logic -
  • create:after_store_logic -
  • create:before_access_check- present on both the create() and store() methods
  • create:after_access_check - present on both the create() and store() methods

    LifecycleEvent::hookInto(['create:before_setup', 'list:before_setup', 'moderate:before_setup'], function() {
        $this->crud->addButton('top', 'create', 'view', 'crud::buttons.create');
    });

    LifecycleEvent::trigger('moderate:after_moderation', [
        'controller' => $controller,
        'operation' => $operation,
    ]);

tabacitu avatar Oct 21 '24 12:10 tabacitu

@tabacitu regarding docs, is this what you had in mind? https://github.com/Laravel-Backpack/docs/pull/622

pxpm avatar Feb 05 '25 16:02 pxpm

Not quite - I think a better place for them would be in the Operations page. After all... they are operation lifecycle hooks, right? I've moved them there, and added a section at the top which explains the operation lifecycle - what gets called when. Might be a little verbose right now, but we can polish. I think it's long overdue that we have an explanation on WHAT gets called WHEN. I'm very happy we finally do.

✅ Made the changes to the docs. Feel free to edit the texts if you feel the need. ✅ Merged the PR.

LFG!!! 💪

tabacitu avatar Feb 07 '25 08:02 tabacitu