Component Controllers
Introduction
Components' Controller classes are, often, nothing more than an extension of
the base class. The base class provides a few utility methods that certain
components leverage, and it provides the render() method that generates a
component's output using the data from its corresponding Model along with its
transpiled template.
All components must have their own controller class, even if it implements no
methods of its own, owing to Nova's extensive use of namespacing. The transpiler
relies on the consistency provided by this approach, as do all classes that
extend the \PMC\Nova\Abstracts\Render_Component abstract class.
The base class provides a single method that can be overridden for a component,
with the remaining methods either marked final or given private visibility.
This ensures consistent behaviour across all components, preventing individual
components from introducing unanticipated side effects.
Sample Component
The following is the <TemplateArticle /> component's controller in its
entirety (at least, as of July 2023).
<?php
/**
* Controller for the `template-article` component.
*
* @package pmc-nova
*/
declare( strict_types = 1 );
namespace PMC\Nova\Style_Guide\Components\Template_Article;
use PMC\Nova\Components\Controller as Base;
/**
* Class Controller.
*/
final class Controller extends Base {}Enqueuing Frontend JavaScript
The base controller class incorporates the
\PMC\Nova\Components\Traits\Controller_Script_Utilities trait to enqueue a
component's scripts. This trait defines an array that is populated with the
names of any script entrypoints required by the component.
To enqueue a component's scripts, simply populate the _entrypoint_names array.
For performance reasons, scripts are enqueued in the footer.
<?php
/**
* Controller for the `byline` component.
*
* @package pmc-nova
*/
declare( strict_types = 1 );
namespace PMC\Nova\Style_Guide\Components\Byline;
use PMC\Nova\Components\Controller as Base;
/**
* Class Controller.
*/
final class Controller extends Base {
/**
* Entrypoints to enqueue for this component.
*
* @var array
*/
protected array $_entrypoint_names = [
'byline',
];
}Nova uses the @wordpress/scripts (opens in a new tab)
package to manage frontend scripts, allowing the plugin to leverage the
utilities that PMC_Scripts provides for enqueueing the built file along with
its dependencies.
_register_hooks() method
As noted in the introduction, the base controller class provides a single method
that individual components can override. That method, _register_hooks(), is
invoked automatically by the base class. It provides a mechanism for components
to register their hook callbacks.
Rendering Output using Model Data
When a component's controller provides hook callbacks, those callbacks should be
static methods. This ensures:
- that callbacks are reusable across all uses of a specific component; and
- that individual controller instances are destroyed after the component is rendered.
If the callback requires data from the model, it should be added to the data object and passed to the hook via the template.
Review the <Image /> and <GutenbergContent /> controllers for examples of
this pattern in practice.
Exception to static rule
If a component's controller must invoke a non-Nova function once, regardless of
how many times the component is used, it can register a non-static hook
callback, but the callback must immediately deregister itself when invoked.
Review the <FeaturedMedia /> component for an example.
This situation is the only time that the controller's _get_inputs() method
may be used.
It is expected that this situation will be rare.
Rendering Output from a Dependent Plugin
In some situations, a component includes output that comes from outside of Nova;
a component could use a Core function or incorporate elements rendered by a
plugin. In these cases, the component's template includes calls to either
do_action() or apply_filters(), and its controller uses the
_register_hooks() method to hook into those actions or filters.
Below is a simplified version of the <Newsletter /> component's controller,
which demonstrates how certain external plugin data is incorporated into the
template output.
<?php
/**
* Controller for the `newsletter` component.
*
* @package pmc-nova
*/
declare( strict_types = 1 );
namespace PMC\Nova\Style_Guide\Components\Newsletter;
use PMC\Nova\Components\Controller as Base;
use PMC\Utils\Theme;
/**
* Class Controller.
*/
final class Controller extends Base {
/**
* Register component-specific hooks.
*
* Typically, this is used to render output from a dependent plugin or other
* source outside of Nova.
*
* @return void
*/
protected function _register_hooks(): void {
add_filter(
'pmc_nova_newsletter_form_action',
[ Theme::get_instance(), 'get_newsletter_api_url' ]
);
// Hooked early to allow `pmc-recaptcha` to append to terms.
add_filter(
'pmc_email_capture_form_terms',
[ Theme::get_instance(), 'get_newsletter_terms' ],
5
);
}
}Additional component whose controllers are worth reviewing:
<Comments />