Subscriber workflow for display hooks
Introduction to new/edit/delete types subscriber Implementation
The next two hook types, 'creating new items' and 'editing existing items' are considered to be
all part of the same workflow. There is little point duplicating the bulk of code required
to process create and edit, therefore we should combine them into a single controller and view.
This is because whether creating a new item, or editing an existing item, we're using
essentially the same form. In 'create' the form starts out empty, and in 'edit' the form
is populated by a database query. We know if we should validate and commit any input data
if the form was submitted or not. And lastly, when we process the form on submit, again,
it's the same process that is used to update, the only difference is we might use an
persist($entity)
as well as flush()
. This is why we can use one controller method and view
for both create and edit actions.
For this reason also, there is no need for separate display and processing methods. For example
edit()
to display edit form, and update()
to validate and update the record, followed by a
redirect simply do not make sense when it can be done easily in one controller method.
Creating a new item
When we create an item, essentially, we visit an edit page with no id in the request. From this we know that the action is not an edit, but a 'create new'. We can determine if it's a brand new form or a submitted form by reading the form submit property. Accordingly, we can notify the system of the hook events.
When displaying a new empty form, we simply trigger a form_edit
in the template with
{{ notifydisplayhooks }}
using a null id.
{{ notifyDisplayHooks('foo.ui_hooks.form_edit', null) }}
The function will return all display hook handlers that respond, sorted according to the administration settings. By default, the return is a string.
When we come to validate a new create form, this means we have received a submit command
in the form. We can then validate our form and then trigger a validate_edit
hook with
$hook = new \Zikula\Bundle\HookBundle\Hook\ValidationHook(new \Zikula\Bundle\HookBundle\Hook\ValidationProviders());
$this->dispatchHooks('…validate_edit', $hook);
$validators = $hook->getValidators();
The validator collection can then be tested for the presence of validation errors or not
with $validators->hasErrors()
. Together with the form submit the method can decide
if it's safe to commit the data to the database or, if the form needs to be redisplayed with
validation errors.
If it's ok simply commit the form data, then trigger a process_edit
Zikula_ProcessHook with
new \Zikula\Bundle\HookBundle\Hook\ProcessHook($name, $id, $url);
The URL should be an instance of Zikula\Bundle\CoreBundle\UrlInterface
which describes how to get the newly created object.
For this reason you must determine the ID of the object before you issue a Zikula\Bundle\HookBundle\Hook\ProcessHook
.
If the data is not ok, then redisplay the template.
form_edit
hooks are displayed in the template with
{{ notifyDisplayHooks('foo.ui_hooks.form_edit', id) }}
Editing an existing item
When when we edit an item, we visit an edit page with an id in the request and the controller will retrieve the item to be edited from the database.
We can determine if we should validate and commit the item or just display the item for editing by reading the form submit property. Accordingly, we can notify the system of the hook events.
When displaying an edit form, we simply trigger a form_edit
hook with with
{{ notifyDisplayHooks('<module>.ui_hooks.<area>.form_edit', id) }}
When we come to validate an edit form, this means we have received a submit command
in the form. We can then validate our form and then trigger a validate_edit
event with
$hook = new \Zikula\Bundle\HookBundle\Hook\ValidationHook(new Zikula_Hook_ValidationProviders());
$this->DispatchHooks('…validate_edit', $hook);
$validators = $hook->getValidators();
The validator collection can then be tested for the presence of validation errors or not
with $validators->hasErrors()
. Together with the form submit the method can decide
if it's safe to commit the data to the database or, if the form needs to be redisplayed with
validation errors.
If it's ok simply commit the form data, then trigger a process_edit
event with
new \Zikula\Bundle\HookBundle\Hook\ProcessHook($name, $id, $url);
If the data is not ok, then simply redisplay the template. The triggered event will pick up
the validation problems automatically as the validation of each handler will persist in
the Zikula\Bundle\HookBundle\Hook\AbstractHookListener
instances unless using an outdated workflow where the
validation method redirects to display methods, in which case you will have to do validation again.
form_edit
hooks are displayed in the template with
{{ notifyDisplayHooks('<module>.ui_hooks.<area>.form_edit', id) }}
Deleting an item
There are many different approaches that can be taken to deleting an item. For example we can add a delete button to an edit form. We usually would have a confirmation screen or we might just use a javascript confirmation. Generally, we would not want to add anything extra to a delete confirmation page, but we certainly need to process a delete action. Ultimately when a controller (that makes use of hooks) deletes an item, it must notify the attached modules to prevent orphaned records. This is done simply by triggering a hookable event with
new \Zikula\Bundle\HookBundle\Hook\ProcessHook($name, $id, $url);
form_delete
hooks are displayed in the template with
{{ notifyDisplayHooks('<module>.ui_hooks.<area>.form_delete', id) }}
Optionally managing display of hooks
In most situations, it is fine to display hooks as they have been loaded by the providers. e.g.
{{ notifyDisplayHooks('<module>.ui_hooks.<area>.form_edit', id) }}
This is because the order the hooks are displayed in can be controlled by the Hook UI via drag and drop.
But if a subscriber must exert more fine-grained control over the display of hooks, this can be done by passing
the fourth argument as true
and then assigning the hooks to a variable like so:
{% set hooks = notifyDisplayHooks('<module>.ui_hooks.<area>.form_edit', id, null, true) %}
Then the subscriber template must loop that variable and display each hook:
{% for area, hook in hooks %}
<div class="z-displayhook my-special-hook-class" data-area="{{ area }}">{{ hook|raw }}</div>
{% endfor %}