Notice: You are browsing the documentation for PrestaShop 9, which is currently in development.
You might want to read the documentation for the current version, PrestaShop 8. Read the current version of this page
In computer programming, CRUD is an acronym for the four basic functions of persistent storage: create, read, update, and delete.
PrestaShop handles several logic objects, like Cart, Product, Order, Customer… among many others. When such objects are stored using a unique identifier, we refer to them as identifiable objects.
In the Back Office, most identifiable objects are managed using forms and page listings that follow the CRUD pattern. When they do, we refer to those forms as CRUD Forms.
Since CRUD forms share a lot of common behavior, PrestaShop provides a common pattern to handle them all the same way. It is based on four main elements, each responsible for a very specific task:
The Form Data Provider takes care of retrieving data to fill out a form. For that, it needs to implement two methods:
To create a Form data provider you must implement the following interface:
\PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataProvider\FormDataProviderInterface
In the example below, you can see a ContactFormDataProvider
that queries the database (in this case, using ObjectModel
) to retrieve data when a specific identifiable object id (in this case, Contact
) is given, and that returns static data with defaults to use when creating a new element.
<?php
namespace PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataProvider;
use Contact;
final class ContactFormDataProvider implements FormDataProviderInterface
{
/**
* Get form data for given object with given id.
*
* @param int $id
*
* @return mixed
*/
public function getData($contactId)
{
$contactObjectModel = new Contact($contactId);
// check that the element exists in db
if (empty($contactObjectModel->id)) {
throw new PrestaShopObjectNotFoundException('Object not found');
}
return [
'title' => $contactObjectModel->name,
];
}
/**
* Get default form data.
*
* @return mixed
*/
public function getDefaultData()
{
return [
'title' => 'service',
];
}
}
This example has been simplified for practical reasons.
The core actually uses the CQRS pattern to retrieve data, instead of ObjectModel
. For more information, have a look at our recommended approach on how to use CQRS in forms.
Don’t forget to register your class as a service, you will need it to use it with the Form builder.
#src/PrestaShopBundle/Resources/config/services/core/form/form_data_provider.yml
prestashop.core.form.identifiable_object.data_provider.contact_form_data_provider:
class: 'PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataProvider\ContactFormDataProvider'
Note: if you use the above snippet of code outside of the PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataProvider
namespace, like in a module, you need to import the classes that come from this namespace.
Example:
use PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataProvider\FormDataProviderInterface;
The Form Builder is used by controllers to build the form that will be shown to users.
PrestaShop provides a default implementation for this object.
It should be enough for most use cases, so you don’t need to create it yourself! It also allows your form to be extended by modules.
The common methods that you will be using are:
$data
and $options
can be used in your form type.$data
and $options
can be used in your form type.In most cases, you can simply reuse the default implementation. All you need to do is declare it as a service and configure it for your form’s needs, by specifying the appropriate Form type and Form data provider.
#src/PrestaShopBundle/Resources/config/services/core/form/form_builder.yml
prestashop.core.form.identifiable_object.builder.contact_form_builder:
class: 'PrestaShop\PrestaShop\Core\Form\IdentifiableObject\Builder\FormBuilder'
factory: 'prestashop.core.form.builder.form_builder_factory:create'
arguments:
- 'PrestaShopBundle\Form\Admin\Configure\ShopParameters\Contact\ContactType'
- '@prestashop.core.form.identifiable_object.data_provider.contact_form_data_provider'
In the example above, we are declaring a specific service for this form based on PrestaShop’s implementation of the Form Builder:
PrestaShop\PrestaShop\Core\Form\IdentifiableObject\Builder\FormBuilder
…which is instantiated using the base factory:
prestashop.core.form.builder.form_builder_factory:create
…using two specific arguments:
Finally, use it in your controller:
<?php
public function createAction($contactId)
{
$contactFormBuilder = $this->get('prestashop.core.form.identifiable_object.builder.contact_form_builder');
$contactForm = $contactFormBuilder->getForm(); // no id as the element does not exist yet
return $this->render('@PrestaShop/Admin/Configure/ShopParameters/Contact/Contacts/create.html.twig', [
'contactForm' => $contactForm->createView(),
]);
}
public function editAction($contactId)
{
$contactFormBuilder = $this->get('prestashop.core.form.identifiable_object.builder.contact_form_builder');
$contactForm = $contactFormBuilder->getFormFor($contactId);
return $this->render('@PrestaShop/Admin/Configure/ShopParameters/Contact/Contacts/edit.html.twig', [
'contactForm' => $contactForm->createView(),
]);
}
Form that is rendered following PretaShop’s UI kit should look like this:
The Form Data Handler is responsible for persisting the data submitted through your form. It implements the following methods:
$id
using the provided dataWhen creating your Form Data Handler you must implement the following interface:
\PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler\FormDataHandlerInterface
In the example below, you can see a ContactFormDataHandler
that uses ObjectModel
to create and update an instance of Contact
:
<?php
namespace PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler;
final class ContactFormDataHandler implements FormDataHandlerInterface
{
/**
* Create object from form data.
*
* @param array $data
*
* @return mixed
*/
public function create(array $data)
{
$contactObjectModel = new Contact();
// add data to object model
// ...
$contactObjectModel->save();
return $contactObjectModel->id;
}
/**
* Update object with form data.
*
* @param int $id
* @param array $data
*/
public function update($id, array $data)
{
$contactObjectModel = new Contact(id);
// update data to object model
// ...
$contactObjectModel->update();
}
}
This example has been simplified for practical reasons.
The core actually uses the CQRS pattern to retrieve data, instead of ObjectModel
. For more information, have a look at our recommended approach on how to use CQRS in forms.
Don’t forget to register your Form Data Handler as a service, you will need it to use it with your Form Handler.
#src/PrestaShopBundle/Resources/config/services/core/form/form_data_handler.yml
prestashop.core.form.identifiable_object.data_handler.contact_form_data_handler:
class: 'PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler\ContactFormDataHandler'
Note: if you use the above snippet of code outside of the PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler
namespace, like in a module, you need to import the classes that come from this namespace.
Example:
use PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler\FormDataHandlerInterface;
The Form Handler is in charge of validating, enriching and saving submitted form data, using the provided Form Data Handler.
PrestaShop provides a default implementation for this object.
It should be enough for most use cases, so you don’t need to create it yourself! It also allows your form to be extended by modules.
It provides two methods:
$id
.Both methods return an instance of FormHandlerResultInterface
that provides information about the process result:
Much like with the Form Builder, in most cases you can reuse the default implementation by declaring it as a service and configuring it according to your form’s needs:
#src/PrestaShopBundle/Resources/config/services/core/form/form_handler.yml
prestashop.core.form.identifiable_object.handler.contact_form_handler:
class: 'PrestaShop\PrestaShop\Core\Form\IdentifiableObject\Handler\FormHandler'
factory: 'prestashop.core.form.identifiable_object.handler.form_handler_factory:create'
arguments:
- '@prestashop.core.form.identifiable_object.data_handler.contact_form_data_handler'
In the example above, we are declaring a specific service for this form handler, based on PrestaShop’s implementation of the Form Handler:
PrestaShop\PrestaShop\Core\Form\IdentifiableObject\Handler\FormHandler
…which is instantiated using the base factory:
prestashop.core.form.identifiable_object.handler.form_handler_factory:create
…using the Form Data Handler that we declared previouly.
Finally, you can use it in your controller like this:
<?php
public function createAction(Request $request)
{
$contactFormBuilder = $this->get('prestashop.core.form.identifiable_object.builder.contact_form_builder');
$contactForm = $contactFormBuilder->getForm();
$contactForm->handleRequest($request);
$contactFormHandler = $this->get('prestashop.core.form.identifiable_object.handler.contact_form_handler');
$result = $contactFormHandler->handle($contactForm);
if (null !== $result->getIdentifiableObjectId()) {
$this->addFlash('success', $this->trans('Successful creation.', 'Admin.Notifications.Success'));
return $this->redirectToRoute('admin_contacts_index');
}
return $this->render('@PrestaShop/Admin/Configure/ShopParameters/Contact/Contacts/create.html.twig', [
'contactForm' => $contactForm->createView(),
]);
}
public function editAction($contactId, Request $request)
{
$contactFormBuilder = $this->get('prestashop.core.form.identifiable_object.builder.contact_form_builder');
// we use getFormFor() instead of getForm() since we now have an id
$contactForm = $contactFormBuilder->getFormFor($contactId);
$contactForm->handleRequest($request);
$contactFormHandler = $this->get('prestashop.core.form.identifiable_object.handler.contact_form_handler');
// we use handleFor() instead of handle() since we now have an id
$result = $contactFormHandler->handleFor($contactId, $contactForm);
if ($result->isSubmitted() && $result->isValid()) {
$this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success'));
return $this->redirectToRoute('admin_contacts_index');
}
return $this->render('@PrestaShop/Admin/Configure/ShopParameters/Contact/Contacts/edit.html.twig', [
'contactForm' => $contactForm->createView(),
]);
}
Let’s analyze the create
flow in the example above.
First, we create the form using the FormBuilder:
<?php
$contactFormBuilder = $this->get('prestashop.core.form.identifiable_object.builder.contact_form_builder');
$contactForm = $contactFormBuilder->getForm();
Then, we merge the form’s data with the one from the Request
(if submitted):
<?php
$contactForm->handleRequest($request);
Afterwards, we process the form (this will save the form in case it was sent, and do nothing otherwise), and save the result of the process for further analysis:
<?php
$contactFormHandler = $this->get('prestashop.core.form.identifiable_object.handler.contact_form_handler');
$result = $contactFormHandler->handle($contactForm);
Now, if the form was actually saved and everything went well, we can show a success message and redirect to the listing page:
<?php
if (null !== $result->getIdentifiableObjectId()) {
$this->addFlash('success', $this->trans('Successful creation.', 'Admin.Notifications.Success'));
return $this->redirectToRoute('admin_contacts_index');
}
Finally, if the form wasn’t submit or if something went wrong, we just show the form.
<?php
return $this->render('@PrestaShop/Admin/Configure/ShopParameters/Contact/Contacts/create.html.twig', [
'contactForm' => $contactForm->createView(),
]);
The edit
flow works exactly the same, with minimal changes:
-$contactFormBuilder->getForm();
+$contactFormBuilder->getFormFor($contactId);
and
-$contactFormHandler->handle($contactForm);
+$contactFormHandler->handleFor($contactId, $contactForm);
This example has been simplified for practical reasons.
The core actually uses CQRS to handle data persistence, which raises a DomainException
in case of a constraint error (for example, if the identifiable object you are trying to edit doesn’t exist). This is handled in the controller by wrapping the code in a try-catch block, then flashing an error message accordingly.
For more details, check out the ContactsController source code on GitHub.
The following schema depicts the complete form flow, including the Domain tier
which uses the CQRS pattern. Learn more about it here.