Skip to content

Commit

Permalink
Update hook documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Pablo Borowicz committed Oct 25, 2021
1 parent e9c0228 commit 9d991f8
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 132 deletions.
85 changes: 8 additions & 77 deletions development/components/hook/_index.md
Original file line number Diff line number Diff line change
@@ -1,91 +1,22 @@
---
title: The Hook component
menuTitle: Hook
chapter: true
---

# The Hook component


The preferred way to customize PrestaShop is using Modules. Modules allow to customize PrestaShop in many ways.

The main path for Module integration are extension points called "Hooks", which are placed throughout the system. Modules can subscribe to hooks in order to provide or alter features.
One of the main ways Modules interact with PrestaShop is through "Hooks" – PrestaShop's event system. Hooks are extension points which are placed throughout the system, allowing subscribing Modules to be notified of system events, inject content, or even alter the behavior of PrestaShop.

There are two types of hooks:

- **Display hooks** – Integrated mainly (but not exclusively) in templates, they allow modules to provide content that will be injected somewhere in a page.
- **Action hooks** – Allow modules to be informed of something happening in the system, and optionally alter the system’s behavior by modifying provided data.

## Subscription registry

PrestaShop's `Hook` component acts as a registry for hook subscriptions. It stores which modules have subscribed to which hook. Multiple modules can subscribe to the same hook, and a single module can subscribe to multiple hooks.

Here is how a Module subscribes to hook `registerGDPRConsent`:

```php
<?php
class Somemodule extends Module
{
public function install()
{
return parent::install() && $this->registerHook('registerGDPRConsent');
}
}
```
You can also register multiple hooks. Here is how a Module subscribes to hook `registerGDPRConsent` and `displayProductAdditionalInfo`:


```php
<?php
class Somemodule extends Module
{
const AVAILABLE_HOOKS = [
'registerGDPRConsent',
'displayProductAdditionalInfo',
];

public function install()
{
return parent::install() && $this->registerHook(self::AVAILABLE_HOOKS);
}
}
```


## Hook dispatcher

Throughout the software, multiple hooks are dispatched: this means at some point the system will look at all modules which subscribe to a given hook and trigger them, waiting for a result.

This is how a hook can be dispatched using `Hook` class:

```php
$id = Hook::exec('actionModifyZoning', ['address_id' => $addressID]);
```

## Module callback

Modules which have previously subscribed to this hook will be notified and will be able to act on this hook. Depending on the hook's nature, they can alter the data being passed, trigger an event or even return a result (a data structure or raw HTML) to be used within PrestaShop.

In order to be notified when the subscribed hooks are dispatched, in addition to subscribing to them, modules must also declare one public callback function per subscribed hook, following this naming schema: `hook<SubscribedHookName>`. That way, when a hook is dispatched, the dispatcher will be able to identify and call the appropriate callback on each subscriber.
- **Display hooks** – Integrated mainly (but not exclusively) in templates, they allow modules to provide content that will be injected at a specific place in a page. Display hook names usually start with _"display"_.
- **Action hooks** – Allow modules to be informed of something happening in the system, and optionally alter the system’s behavior by modifying the provided data. Action hook names usually start with _"action"_.

This means that in order to fully subscribe to a hook, a module must call `registerHook()` **and** declare a callback. For example:
## In this section

```php
<?php
class Somemodule extends Module
{
public function install()
{
return parent::install() && $this->registerHook('registerGDPRConsent');
}
{{% children /%}}

public function hookRegisterGDPRConsent($parameters)
{
// This is where you can modify/alter the behavior of PrestaShop.
// The content of $parameters will depend on what is sent when the hook is dispatched.
}
}
```
## Related reading

{{% notice tip %}}
Notice how hook names start with a lower case letter (`registerGDPRConsent`), and hook callbacks use the capitalized name `hookRegisterGDPRConsent`.
{{% /notice %}}
- [List of hooks]({{< relref "/8/modules/concepts/hooks/list-of-hooks" >}})
94 changes: 94 additions & 0 deletions development/components/hook/dispatching-hook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
title: Dispatching a Hook
weight: 20
---

# Dispatching a Hook

Hooks are placed everywhere throughout the software. Whenever a given hook is dispatched, the Hook component will check which modules subscribed to that hook and notify them by successively calling each subscriber's callback.

Hooks are dispatched using the `Hook::exec()` function.

```php
Hook::exec(
string $hookName,
array $arguments,
?int $moduleId = null,
bool $returnArray = false,
bool $checkExceptions = true,
bool $usePush = false,
int $shopId = null,
bool $chain = false
): mixed
```

This method returns the result of all hook notification callbacks. The return type will depend on the notification callbacks and the value of `$returnArray`.

##### Parameters

`$hookName`
:
Name of the hook to dispatch.

`$arguments`
:
Parameters to send with the hook event to subscribers.

`$moduleId`
:
If defined, only the specified module will be notified for this hook event.
**Note:** the module needs to have subscribed to the hook already.

`$returnArray`
:
This parameter governs the return type for this method. By default, the return values from each callback will be concatenated and returned. If `$returnArray` is set to true, then this method will return an associative array of return values, indexed by module name.
Note that this argument is incompatible with `$chain` and will be disabled automatically disabled if chaining is enabled.

`$checkExceptions`
:
Modules can opt out from being notified for a hook if it's triggered in specific pages (module hook exceptions). If this parameter is set to false, these exceptions will be ignored and the modules will be notified anyway.
**Note:** if the hook is triggered in the Back office, disabling this feature will bypass access restrictions to modules, allowing modules to be notified of the hook event even when the current employee's access restrictions to that module wouldn't normally allow it.

`$usePush`
:
If enabled, the hook dispatcher will wait for the push file of any subscribed module to be updated before notifying it of the hook event.

`$shopId`
:
This parameter is related to multistore. If set, the shop context will be switched to the specified shop for the duration of the dispatch.

`$chain`
:
If this parameter is set to true, then the result of each subscriber callback will be used as parameters of the next subscriber's callback.

##### Example

```php
$id = Hook::exec('actionModifyZoning', ['address_id' => $addressID]);
```

In the example above, we dispatch the `actionModifyZoning` hook, along the `address_id` parameter. `$id` will contain the concatenation of all the values returned by subscribers' callbacks (if any).

## Dispatching a hook from a template

Hooks can be dispatched from templates as well.

### Smarty

```smarty
{hook h='displayProductPriceBlock' product=$product type="unit_price"}
```

### Twig

```twig
{{ renderhook('displayAdminProductsMainStepLeftColumnMiddle', { 'id_product': productId }) }}
```

## Creating a new hook

Dispatching a new hook doesn't require any special steps, you can simply choose a hook name, and call your new hook from wherever you need – even from within a module!

{{% notice tip %}}
Remember that hook names are global – choose your hook names carefully to reduce risks of collision with other modules or future Core hooks.
{{% /notice %}}
80 changes: 27 additions & 53 deletions development/components/hook/register-new-hook.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,23 @@
---
title: Register a new hook
weight: 10
title: Adding a new Core hook
weight: 30
---

# How to register a new Hook in PrestaShop

This is basically three steps:

* dispatch the hook in the code (in templates or PHP classes/files);
* update the hooks xml definition of Installer;
* update the hooks table for "Auto Upgrade" system;

## Dispatching hooks

Most of the time, you will dispatch the hook using an instance of `HookDispatcher`. It can be retrieved from the service container and/or injected, as it's done for example in Form Handlers:

```php
<?php
final class FormHandler extends AbstractFormHandler
{
/* [...] */
public function getForm()
{
$formBuilder = $this->formFactory->createBuilder()
->add('general', GeneralType::class)
->add('upload_quota', UploadQuotaType::class)
->add('notifications', NotificationsType::class)
->setData($this->formDataProvider->getData())
;
$this->hookDispatcher->dispatchWithParameters(
'displayAdministrationPageForm',
['form_builder' => &$formBuilder]
);
return $formBuilder->setData($formBuilder->getData())->getForm();
}
/* [...] */
}
```
# How to add a new Core Hook

Although hooks are automatically created the first time they are subscribed to, new hooks is added to the Core itself need to be properly registered and documented.

Here are the steps you need to follow.

## 1. Dispatch the new hook

## Hooks definition file
The very fisrt step is just to dispatch the new hook wherever you need it, as explained in [Dispatching a Hook]({{< relref "dispatching-hook" >}}).

During the installation, hooks listed in the `install-dev/data/xml/hook.xml` file are stored on database and made available in PrestaShop. Even if this step is not a requirement – hooks can be declared from templates or generated dynamically – it's a good practice to do it. Also, every hook registered in Database will be displayed in the Hook debugger, so it will help the developer figure out which hooks are available.
## 2. Update the Hooks definition file

All known hooks are registered in the `ps_hook` database table. This table is populated with Core hooks during the installation, by reading the `install-dev/data/xml/hook.xml` file, then during runtime every time a new hook is used.

Having Core hooks registered is important for two main reasons. First, every hook registered in database will be displayed in the Hook debugger, so it will help developers figure out which hooks are available on a given page. Second, known hooks are displayed in the Positions page, allowing to transplant modules from one hook into another (this is especially useful for display hooks).

Each hook has a name, a title and a definition. They are identified by an additional id attribute in XML, which is the same as its name.

Expand All @@ -53,38 +30,35 @@ Each hook has a name, a title and a definition. They are identified by an additi
<field name="description"/>
</fields>
<entities>
<hook id="...">
<name>...</name>
<title>...</title>
<description>...</description>
</hook>
<hook id="actionMaintenancePageFormSave">
<name>actionMaintenancePageFormSave</name>
<title>Processing Maintenance page form</title>
<description>This hook is called when the Maintenance Page form is processed</description>
</hook>
<hook id="...">
...
</hook>
</entities>
</entity_hook>
</xml>
```

{{% notice tip %}}
Always add new hooks at the bottom of the list, as hooks are registered sequentially.
{{% /notice %}}
Add new hooks at the bottom of the list, as hooks are registered sequentially.

## 3. Prepare database update for shop upgrades

## Prepare database update for auto upgrades
The previous step only adds the hook to _new_ shops. We also need to register the hook to shops that upgrade from a previous version.

The last step is to describe the update process for the auto upgrade module – essentially, the insertion of new hooks in *hooks* table.
Locate the X.Y.Z.sql file that refers to the PrestaShop version that will include your change: for instance, if the release expected to include this change is `1.7.5.0`, locate that file in `upgrade/sql` folder in the [autoupgrade](https://github.com/PrestaShop/autoupgrade) module.
The last step is to insert the new hooks in the `ps_hooks` table using the upgrade system. Locate the X.Y.Z.sql file that refers to the PrestaShop version that will include your change: for instance, if the release expected to include this change is `1.7.5.0`, locate that file in the `upgrade/sql` folder from the [autoupgrade](https://github.com/PrestaShop/autoupgrade) module.

{{% notice tip %}}
This process is explained here: [Structure and content upgrades]({{< ref "/8/development/database/structure.md#structure-and-content-upgrades" >}})
{{% /notice %}}

Then add the corresponding SQL commands to add new hooks:
Once you have located the file, add the corresponding SQL commands to add new hooks:

```sql
INSERT IGNORE INTO `PREFIX_hook` (`id_hook`, `name`, `title`, `description`, `position`) VALUES
(NULL, 'displayAdministrationPageForm', 'Manage Administration Page form fields', 'This hook adds, update or remove fields of the Administration Page form', '1'),
(NULL, 'actionMaintenancePageFormSave', 'Processing Maintenance page form', 'This hook is called when the Maintenance Page form is processed', '1');
INSERT IGNORE INTO `PREFIX_hook` (`name`, `title`, `description`) VALUES
('displayAdministrationPageForm', 'Manage Administration Page form fields', 'This hook adds, update or remove fields of the Administration Page form'),
('actionMaintenancePageFormSave', 'Processing Maintenance page form', 'This hook is called when the Maintenance Page form is processed');
```
Loading

0 comments on commit 9d991f8

Please sign in to comment.