Advanced Express Controller Customization

There's a lot that's possible with the new Express Form Controller architecture baked into concrete5 8.2. In addition to the powerful theming possibilities described in the previous doc, you can use Express Controllers to customize other aspects of the Form, including

  • Implementing custom validation logic
  • Implementing a custom notifier for when the form is submitted
  • Creating a custom response handler, useful for handling redirects on submission
  • Running custom code whenever an object is saved, updated or deleted.

Create a Custom Controller and Attach it To your Express Entity

First, you'll need to create a custom controller and attach it to your express entity (or set it up as the default controller for all express objects). There is detailed information in Customizing Form and Attribute Markup with Custom Contexts on how to do just that. Check out that documentation first, and then continue.

Once you have a custom controller, you can start to modify some of the above aspects of how the form functions, by overriding some of the default logic that the form provides.

Custom Validation Logic

Need to do something custom when your express form is submitted? Overriding and/or extending the validation logic for an express form is simple.

Custom Form Processor

In order to create custom validation logic when an Express form is submitted, first you'll need to create a custom class that implements the Concrete\Core\Express\Form\Processor\ProcessorInterface. Let's create one in our package. Create a file named FormProcessor.php at packages/my_site/src/MySite/Express/Form/Processor/FormProcessor.php. Let's make this class blank for now, but make it extend the standard processor in the core:

<?php
namespace MySite\Express\Form\Processor;

use Concrete\Core\Express\Form\Processor\StandardProcessor;

class FormProcessor extends StandardProcessor
{

}

Now, let's make our custom controller return this processor. Open packages/my_site/src/MySite/Express/Controller/FormController.php and add the following code to it:

public function getFormProcessor()
{
    return $this->app->make('\MySite\Express\Form\Processor\FormProcessor');
}

This will deliver this custom form processor any time the form processor is required.

Custom Validator

Now you'll need to create a custom class that implements the Concrete\Core\Express\Form\Validator\ValidatorInteface. Let's create one in our package. Create a file named FormProcessor.php at packages/my_site/src/MySite/Express/Form/Validator/FormValidator.php. Let's make this class blank for now, but make it extend the standard express form validator in the core:

<?php
namespace MySite\Express\Form\Validator;

use Concrete\Core\Express\Form\Validator\StandardValidator;

class FormValidator extends StandardValidator
{

}

Now, let's make our custom form processor return this processor. Open packages/my_site/src/MySite/Express/Processor/FormProcessor.php and add the following code to it:

public function getValidator(Request $request)
{
    $validator = new \MySite\Express\Form\Validator\FormValidator($this->app, $this->app->make('error'), $request);
    return $validator;
}

Customize the Validator

Now that your controller is returning your custom processor, and your custom processor is returning a custom validator, you're free to modify the FormValidator class to validate in the way you see fit. Just add routines using StandardValidator::addRoutine. Routines are any object that implements the Concrete\Core\Express\Form\Validator\Routine\RoutineInterface.

Custom Responses

The form processor is also responsible for delivering custom responses. If you want to programmatically decide where to redirect an Express Form (for example) or its content type, just customize the deliverResponse method of your form processor class. For example, if you add this code to your custom MySite\Express\Form\Processor\FormProcessor

public function deliverResponse(Entry $entry, $requestType, RedirectResponse $response = null)
{
    $data['success'] = 1;
    $response = new \Symfony\Component\HttpFoundation\JsonResponse($data);
    return $response;
}

Your form will return a JSON string instead of redirecting to a new location.

Custom Notification

When setting up an Express Form block, we have the ability to specify an email address for someone to receive an email. This is handy, but what if we want to notify people in a dynamic way? What if we want to send email to different people based on the value of a select attribute? Using a custom notification handler we can do something just like that.

In order to craft custom notification functionality, you'll need to implement a custom notification class that implements the Concrete\Core\Express\Entry\Notifier\NotifierInterface. Let's create that class. Create a file named FormNotifier.php at packages/my_site/src/MySite/Express/Entry/Notifier/FormNotifier.php. Let's make this class blank for now, but make it extend the standard express form notifier in the core:

<?php
namespace MySite\Express\Entry\Notifier;

use Concrete\Core\Notification\Notifier\StandardNotifier;

class FormNotifier extends StandardNotifier
{

}

Now that we have a custom form notifier, let's return this notifier in our controller. Open packages/my_site/src/MySite/Express/Controller/FormController.php and add the following code to it:

public function getNotifier(NotificationProviderInterface $provider = null)
{
    $notifier = parent::getNotifier($provider);
    return $notifier;
}

Obviously this isn't very useful – it simply implements the method the same way the parent did. We need to add some custom logic after the $notifier is retrieved.

NotificationProviderInterface

The first argument provided to the getNotifier() method is actually a Concrete\Core\Express\Entry\Notifier\NotificationProviderInterface. This is the controller of the form block when used on the front-end. We can then retrieve all the notifications that we're supposed to send.

Custom Example

Let's say our Express Form included a page selector attribute, and we allow users to select a page that they're interested in. The page itself has a custom attribute for contact_email, and we want to notify that email address whenever someone fills out the form and selects that page. Here's how we'd do it. First, we'd create an empty class that implements the Concrete\Core\Express\Entry\Notifier\NotifierInterface. Let's name it MyFormSubmissionEmailNotification and put it in packages/my_site/src/MySite/Express/Entry/Notifier/Notification/MyFormSubmissionEmailNotification.php.

<?php
namespace MySite\Express\Entry\Notifier\Notification;
use Concrete\Core\Express\Entry\Notifier\NotifierInterface;

class MyFormSubmissionEmailNotification implements NotifierInterface
{

}

Next, import this class into your FormNotifier class:

namespace MySite\Express\Entry\Notifier;
use MySite\Express\Entry\Notifier\Notification\MyFormSubmissionEmailNotification;

and in that same class implement the getNotifier method:

public function getNotifier(NotificationProviderInterface $provider = null)
{
    $notifier = parent::getNotifier();
    // Add our custom notification onto the end of the standard notifications.
    $notifier->getNotificationList()->addNotification(new MyFormSubmissionEmailNotification($this->app));
    return $notifier;
}

Finally, let's implement our custom logic in the only method that the NotifierInterface requires, notify():

<?php
namespace MySite\Express\Entry\Notifier\Notification;
use Concrete\Core\Express\Entry\Notifier\NotifierInterface;
use Concrete\Core\Entity\Express\Entry;

class MyFormSubmissionEmailNotification implements NotifierInterface
{

    public function notify(Entry $entry, $updateType)
    {
        $app = \Core::make('app');
        $relatedPageID = $entry->getAttribute('related_page');
        if ($relatedPageID) {
            $relatedPage = \Page::getByID($relatedPageID);
            if (is_object($relatedPage) && !$c->isError()) {
                $emails = $relatedPage->getAttribute('emails');
                if ($emails) {
                    $mh = $app->make('mail');
                    $mh->to($emails);
                    $mh->from($this->getFromEmail());
                    $mh->replyto($this->getReplyToEmail($entry));
                    $mh->addParameter('entity', $entry->getEntity());
                    $mh->addParameter('formName', $this->getFormName($entry));
                    $mh->addParameter('attributes', $this->getAttributeValues($entry));
                    $mh->load('some_custom_email_template', 'my_site');
                    $mh->setSubject(t('Contact Form Submission'));
                    $mh->sendMail();
                }
            }
        }
    }
}

Hopefully this is pretty self-explanatory: When the notification is sent, we run the notify() method and pass the current express object entry into it. You can then use this object to retrieve information about where you're supposed to send email. We grab the related page attribute from the express object entry, and use it to get the concrete5 page to inspect. Then, on that page, we get the emails attribute mentioned earlier, and use that to send a customized email.

Running Custom Code When an Entry is Created, Updated or Deleted

Using Controllers, its possible to create custom code at different point into the Express Object Entry lifecycle. Simply create a custom Express Entry Manager, and return it when working with the Controller. This is any object that implements the Concrete\Core\Express\Entry\EntryManagerInterface. Let's create one in our package.

Create a file named EntryManager.php at packages/my_site/src/MySite/Express/Entry/EntryManager.php. Let's make this class blank for now, but make it extend the standard processor in the core:

<?php
namespace MySite\Express\Entry;

use Concrete\Core\Express\Entry\Manager as BaseEntryManager;

class EntryManager extends BaseEntryManager
{

}

Now, let's make our custom controller return this processor. Open packages/my_site/src/MySite/Express/Controller/FormController.php and add the following code to it:

use Symfony\Component\HttpFoundation\Request;
use MySite\Express\Entry\EntryManager;

and then this:

public function getEntryManager(Request $request)
{
    return new EntryManager($this->app->make(EntityManager::class), $request);
}

This will deliver this custom entry manager any time it is required. The EntryManagerInterface specifies a few methods that are required, and you can extend or override the core ones as you see fit:

function addEntry(Entity $entity);
function deleteEntry(Entry $entry);
function saveEntryAttributesForm(Form $form, Entry $entry);

Just add your custom code before or after the standard code in the entry manager runs, and you'll be able to control exactly how your entity behaves during the add, update or delete cycle.

Loading Conversation