This document assumes you’ve installed hiraeth/bootstrap or hiraeth/actions in addition to the selective package recommendations at the top of the pages page.

Actions are PHP classes which perform the control logic for incoming requests. Their primary responsibility is usually determining appropriate services and how to map incoming request data like route parameters and the request body to those services to get or modify data. Additionally, they may perform specific request control operations like authentication, validation, etc (even if only by calling other services) and returning appropriate responses.

Actions are like controllers in other frameworks, but while most controllers may contain a whole collection entry methods, actions are only a single method (__invoke()). In principle, any callable can therefore be an action. As calsses, however, traits, abstraction and dependency injection are all better ways to provide re-usability than loading large / complex controllers that handle lots of requests.

Continuing with our dictionary example, let’s create an action that returns an actual 404 response in local/actions/Words/Get.php.

namespace Words;

class Get extends \AbstractAction
{
	public function __invoke($word)
	{
		$template = sprintf('@pages/words/%s.html', $word);

		if (!$this->templates->has($template)) {
			return $this->response(404);
		}

		return [];
	}
}

Although Hiraeth provides PSR-0 loading on the local, we strongly discourage PSR style class loading as we don’t think the organizaton of code structure should dictate the organization of file structure. It is common for Hiraeth libraries and projects to organize files by type, e.g. actions, entities, repositories, etc and code by “module.” As we’ve done above, we put this in the actions sub-directory, so go ahead and do that composer dump to find new classes, it takes less than a second.

Calling Actions

To call an action from a page, you can use Twig’s do tag along with the action() function provided by hiraeth/actions. Let’s update our hypothetical dictionary application’s resources/pages/words/@get.html file to the following:

{% extends '@layouts/main.html' %}

{% do action('Words:Get') %}

{% block body %}
	{% include '@pages/words/' ~ parameters.word ~ '.html' %}
{% endblock %}

There’s no need to pass parameters explicity to an action so long as the arguments for the action name match the parameter name.

If you need to map parameters explicitly either due to naming differences or to pass options not present in URL parameterization, you can pass an associative array as the second argument to the call. This contrived example shows effectively what the default mapping would be:

{% do action('Words:Get', {
	word: parameters.word
}) %}

Working In Actions

Although not strictly necessary, by extending the AbstractAction class, your actions will have a lot of additional context and features available to them that make working with requests and responses easier. Keep in mind actions are fully dependency injected, so you can always typehint __construct() parameters to get additional dependencies:

__construct(
	protected Repository $words
) {}

Accessing the Request

You can easily access the PSR-7 server request object (among other things) that represents the incoming server request via the request property:

public function __invoke()
{
	if ($this->request->getMethod() == 'POST') {
		...
	}
}

For a full list of available methods, their arguments, and returned data types available on the request object, see the PSR-7 server request interface documentation.

Accessing the Request Data

It is 100% possible to access all request data via the server request object through standard PSR-7 interfaces. Depending on the complexity of the incoming data, this may actually be necessary at times to distinguish between query parameters, post data, files, etc. However, more common is the need to have this information easily combined together or to get default values if it hasn’t been sent.

Hiraeth’s AbstractAction provides two methods for easily accomplishing these tasks.

Checking If Data Exists

If you need to check if a parameter has been sent (regardless of via query parameter, request body, or custom attributes), you can use the has() method:

if ($this->has('file')) {
	...
}

Getting Data

To get the data by name, you can use the get() method in very much the same way.

$this->get('file')

If different data sources (e.g. query parameters vs. request body) define the same name for a piece of data, Hiraeth will defer depending on the request method. Query parameters will overload request body on a GET, for example, while the request body will overload query paremters on all other methods. In both case, attributes added to the request object will overload anything (as these are only assigned internally).

In addition to being able to get data that has been sent, you can also provide a default in the event it hasn’t. This is common, for example, in the case of pagination, where you likely want to default to a value of 1 if a specific page has not been requested:

$this->get('page', 1)

Similar to Hiraeth’s environment data access methods, the get() will also implicitly cast the data (which is a string if coming from query parameters) based on a default provided. If the default is an object, but the parameter has been supplied, it will try to create a new instance of that object and pass the parameter as the first argument, for example:

$start_date = $this->get('start_date', new \DateTime('-30 Days'));

If the start date it was provided in the request then $start_date will be a DateTime object. Finally, if you want to get all data, just don’t pass any arguments at all, keeping in mind it’s then on you to cast it if necessary.

Security

The functionality of get() is extremely useful, however, it should be noted that whenever working with user entered data that you must be security cautious. If you want to prevent, for example, any query parameters from leaking into POST data when using this method, you can do the following before all calls to get() or has():

$this->load(TRUE);

This will prevent the request method based overloading and completely exclude data not applicable to the request method. Note, excluded data is still accessible via the request property as the full PSR-7 representation of the request.

Returning

When actions are called they are expected to return either data (which will be added to the page’s context) or a response which will interrupt the page rendering and provide some alternative.

In the case of the former, you simply return an associative array of the data you want to make available to the template after calling the action:

return [
	'foo' => 'bar'
];

In the case of the latter, there are several helpers which enable you to return alternative responses.

Redirects (With Parameters)

return $this->redirect(
	'/people',
	[
		'page' => 1
	]
);

The default status code on a redirect will be a 303, to change it, just wrap it:

return $this->response(
	302,
	$this->redirect(
		'/people',
		[
			'page' => 1
		]
	)
);

Templates

You can return an alternative template with their own response code (as seen in our opening example) like so:

return $this->response(
	404,
	$this->template('@pages/not-found.html')
);

Routing

While Hireath promotes the view-first approach, many people may prefer a more traditional approach where controllers (or actions in our case) are the entry point for handling a given URL. It may also be the case, that you want to use Twig to template non-HTML formats like JSON. For this, Hiraeth provides more traditional routing functionality that can route directly to actions or arbitrary templates.


Add a Route