Links

1. Introduction

Url structures are a significant part of web applications. For this reasons the Adventure PHP Framework (APF) provides a framework-like solution to easily generate and manipulate urls.

Within the past (including release 1.13) the APF provided the LinkHandler and FrontcontrollerLinkHandler components that have provided static methods to generate new urls or provide existing ones. Manipulation of existing urls is the basis of loose coupling of software components using the url. If every component just changes "their" own set of parameters and remains the "others" un-touched several components can be managed within one request at the same time.

One big disadvantage of the statical approach is that the url layout and the generation mechanism must be kept synchronous for all components. As a last consequence, this breaks the separation of url layout and software as described in the APF's filter concept.

For this reason, the link generation mechanism has been redesigned in 1.14 to easily implement custom url layouts (see wiki page) that can be injected to existing applications.

2. Architecture

The architecture of the link generation mechanism starting with release 1.14 introduces a separation between the construction of an url and the url itself (class Url). Beyond, the LinkGenerator component is able to create "normal" and front controller action urls.

The real "work" is done by an LinkScheme implementation that is provided the url representation created by the developer. The link scheme's code then formats the url as described in the following picture:

Link generation with the APF

2.1. Url

The Url class represents any url according to RFC 1630 which is independent from the formatting that is done later on. This enables you to generate every link format out of the url representation or from any "ordinary" url.

The class looks as follows:

PHP code
final class Url { const DEFAULT_HTTP_PORT = '80'; const DEFAULT_HTTPS_PORT = '443'; private $scheme; private $host; private $port; private $path; private $query = array(); public function __construct($scheme, $host, $port, $path, array $query = array()) { $this->scheme = $scheme; $this->host = $host; $this->port = $port; $this->path = $path; $this->query = $query; } ... }

Please have a look at the following table to get an idea about the class' fields:

Scheme Host Port Path Query
http:// example.com :80 /news-archive/2011 ?id=123&print=true

Both for "normal" as for "rewritten" urls request parameters ("Query") can be defined as desired, since the url abstraction is format-independent.

2.2. LinkGenerator

The LinkGenerator class is responsible for link formatting. It delegates the real work to the globally configured or applied LinkScheme.

Basically, the LinkGenerator is an abstraction component concerning the link generation. In case each software component uses the link generator (as it is the case for all APF components) changing the link scheme causes an on-the-fly adaption of the url layout.

The LinkGenerator looks like this:

PHP code
final class LinkGenerator { private static $LINK_SCHEME; ... public static function setLinkScheme(LinkScheme $linkScheme) { self::$LINK_SCHEME = $linkScheme; } ... public static function generateUrl(Url $url, LinkScheme $scheme = null) { return ... } public static function generateActionUrl(Url $url, $namespace, $name, array $params = array(), LinkScheme $scheme = null) { return ... } }

As mentioned in the introduction the LinkGenerator features a special method to generate action urls to explicitly address front controller actions.

generateActionUrl() is intended to create an explicit front controller action call. Actions that should remain within the url for consistency reasons are automatically embedded into the generated url by the link scheme implementations shipped with the APF. Details can be taken from chapter 2.4.3 within the front controller documentation.

2.3. LinkScheme

The LinkScheme interface defines the structure of every dedicated link formatting component. Each implementation must be able to create a url by an applied Url or a Url together with action parameters.

In other words: a LinkScheme represents the actual url layout concerning the generation of links. Please note, that an url layout is only fully functional in combination with a suitable input filter. This is because urls must be interpreted to be able to control the application from the outside.

The interface's structure looks like this:

PHP code
interface LinkScheme { public function formatLink(Url $url); public function formatActionLink(Url $url, $namespace, $name, array $params = array()); public function setEncodeAmpersands($encode); public function getEncodeAmpersands(); }

formatLink() is intended to generate "normal" urls, formatActionLink() generates front controller urls. setEncodeAmpersands() and getEncodeAmpersands() can be used to configure the link scheme.

Within the present version of the APF two implementations of the interface are included: DefaultLinkScheme and RewriteLinkScheme. The former is used to format "default" urls, the latter one handles rewritten urls according to the layout definition within chapter rewrite urls of the filter documentation and or the url layout section of the front controller documentation.

3. Usage

The following chapters present the usage of the shipped components as well as the link schemes.

3.1. Creation of the url representation

In order to create a formatted url LinkGenerator::generateUrl() expects an instance of the Url class to be passed as an argument. To ease creation Url can be created as follows:

PHP code
// creating the url abstraction from the current url $url = Url::fromCurrent(); // creating an absolute url from the current one $url = Url::fromCurrent(true); // creating the url abstraction from the referrer url $url = Url::fromReferer(); // creating ab absolute url from the referrer url $url = Url::fromReferer(true); // creating the url from a any string $url = Url::fromString('http://example.com/pages/news'); $url = Url::fromString('http://example.com/pages/news?page=3'); $url = Url::fromString('/pages/news?page=3'); $url = Url::fromString('?page=news'); // using the constructor to create the url representation $url = new Url('http', 'example.com', null, '/pages/news', array()); $url = new Url('http', 'example.com', null, '/pages/news', array('page' => 3)); $url = new Url(null, null, null, '/pages/news', array('page' => 3)); $url = new Url(null, null, null, null, array('page' => 3));

After creation, the url represetnation can be manipulated using the methods

  • setScheme()
  • setHost()
  • setPort()
  • setPath()
  • setQuery()
  • mergeQuery()
  • setQueryParameter()
  • resetQuery()

The usual application case of the link generation is the manipulation of an existing url. For this reason, the current Url can be configured with the desired parameters. After that, it can be passed to the LinkGenerator for formatting:

PHP code
// add or overwrite the current parameters using an associative array $link = LinkGenerator::generateUrl(Url::fromCurrent()->mergeQuery(array( 'page' => 5, 'print' => 'true'))); // add or overwrite the current parameters using single statements $link = LinkGenerator::generateUrl(Url::fromCurrent() ->setQueryParameter('page', 5) ->setQueryParameter('print', 'true'));
The Url class provides a fluent interface for all setter and factory methods. So, configuration can be done by combining various calls.

In addition, you can also use the creation methods described in the previous chapter since the result is always an instance of the Url class. After creation the parameters and properties can be adapted like this:

PHP code
$link = LinkGenerator::generateUrl(Url::fromString('/pages/news?page=3&print=true') ->setScheme('https') ->setHost('example.com') ->setQueryParameter('page', 4) ->setQueryParameter('print', null)); $link = LinkGenerator::generateUrl(Url::fromString('/pages/news?page=3&print=true') ->setScheme('https') ->setHost('example.com') ->mergeQuery(array('page' => 4, 'print' => null)));

Using the DefaultLinkScheme the result in both cases is:

Code
https://example.com/pages/news?page=4
The link schemes delivered with the APF include the logic to remove a parameter in case it's value is null. Since this is a special interpretation of these link schemes this is no common behaviour of all LinkScheme implementations!

If you intend to generate only few links using a dedicated link scheme (e.g. for AJAX or external urls) you are able to pass a special link scheme to the generateUrl() call. This call then overwrites the global link scheme:

PHP code
$ajaxUrl = LinkGenerator::generateUrl(Url::fromString('news.php') ->setQueryParameter('page', 4), new AjaxLinkScheme());

As you can take from the code block the second argument takes a newly generated link scheme. For convenience, you can also re-use an existing link scheme to be configured for the current case:

PHP code
$scheme = LinkGenerator::cloneLinkScheme(); $scheme->setSpecialParameter(true); $specialUrl = LinkGenerator::generateUrl(Url::fromCurrent() ->setQueryParameter('page', 4), $scheme);

Each front controller action has a property that defines whether the action's representation should be kept in the url or not. This is - as described in chapter 2.4.3 - a technical way, the decoupling of software components via the url is realized for the APF. For the developer this possibility is especially interesting in case an action should remain within the url for application management purposes but the application executed "within" this action should not know about that.

The functionality described in the last paragraph is already part of the APF link scheme implementations. In case of custom link schemes the automatic inclusion of front controller actions must be implemented as desired!

Nevertheless, this chapter mainly deals with links that address dedicated actions that are not marked to be kept within each url. For this reason, the LinkGenerator::generateActionUrl() is provided. This method can generate action links by a basic url, the action's namespace and name as well as a set of parameters:

PHP code
$url = LinkGenerator::generateActionUrl(Url::fromString('/pages/news'), 'tools::media', 'streamMedia', array( 'namespace' => 'modules_usermanagement_pres_images', 'extension' => 'png', 'filebody' => 'icon_delete' ) );

The resulting url consists of a base url (created from string) and a dedicated action instruction. Using the RewriteLinkScheme results in the subsequent url:

Code
/pages/news/~/tools_media-action/streamMedia/namespace/modules_usermanagement_pres_images/extension/png/filebody/icon_delete

Taking the DefaultLinkScheme the url is as follows:

Code
/pages/news?tools_media-action:streamMedia=namespace:modules_usermanagement_pres_images|extension:png|filebody:icon_delete

In case te base url already contains a permanent action instruction the explicit action instruction is added at the end. The main difference between the action link generation and the normal method is the fact, that the action parameters are passed as independent arguments.

Moreover, the generation of action urls can be done using adapted base urls. For this reason all methods described in chapter 3.2 can be used.

As you have seen for LinkGenerator::generateUrl(), LinkGenerator::generateActionUrl() accepts a custom link scheme, too:

PHP code
$url = LinkGenerator::generateActionUrl(Url::fromString('/pages/news'), 'tools::media', 'streamMedia', array( 'namespace' => 'modules_usermanagement_pres_images', 'extension' => 'png', 'filebody' => 'icon_delete' ), new SpecialLinkScheme() );

As already mentioned in the previous chapters the APF provides two link schemes: DefaultLinkScheme and RewriteLinkScheme. They implement the LinkScheme interface and can be configured for some special cases.

At present, the configuration switches regard the activation or deactivation of the ampersand (&) encoding feature. In case you do not want the ampersand to be encoded, you can do something like this:

PHP code
// use dedicated link scheme with special configuration $scheme = LinkGenerator::cloneLinkScheme(); $scheme->setEncodeAmpersands(true); $url = LinkGenerator::generateUrl(..., $scheme); // adapt global link scheme $scheme = LinkGenerator::getLinkScheme(); $scheme->setEncodeAmpersands(true); LinkGenerator::getLinkScheme($scheme); $url = LinkGenerator::generateUrl(...);

If you are using custom link schemes the two configuration possibilities can be used as well.

4. Enhancement

The above described concept has been designed for flexibility but also for enhancement. Separation of the LinkGenerator from it's LinkSchemes enables you to inject an absolutely new link scheme without having to change the functionality.

In order to guarantee such flexibility it is important to use a generic internal url layout that can be transformed to the desired representation of the currently active link scheme.

The next two chapters describe the enhancement of the link generation mechanism of the APF.

A link scheme is defined by the LinkScheme interface. In order to implement a custom scheme you have to implement the methods formatLink() and formatActionLink() as well as setEncodeAmpersands() and getEncodeAmpersands().

The former two functions are intended to generate the desired url layout from the applied url abstraction. Having a look at the next code sample you can see an implementation, that uses the current page identifier as an url path and adds all further parameters as a "real" request parameters. Sample:

Code
/news?page=2

The code to generate this layout is as follows:

PHP code
class SpecialLinkScheme implements LinkScheme { private $encodeAmpersands = true; public function __construct($encodeAmpersands = true) { $this->encodeAmpersands = $encodeAmpersands; } public function formatLink(Url $url) { $ampersand = $this->encodeAmpersands ? '&' : '&'; $link = '/' . $url->getQueryParameter('page'); $params = array(); foreach ($url->getQuery() as $name => $value) { if (!empty($value)) { $params[] = $name . '=' . $value; } } if (count($params) > 0) { $link .= '?' . implode($ampersand, $params); } return $link; } public function formatActionLink(Url $url, $namespace, $name, array $params = array()) { $link = $this->formatLink($url); $link .= strpos('?') !== false ? '&' : '?'; $link .= str_replace('::', '_', $namespace) . '-action:' . $name; $actionParams = array(); foreach ($params as $name => $value) { if (!empty($value)) { $actionParams[] = $name . ':' . $value; } } if (count($actionParams) > 0) { $link .= '=' . implode('|', $actionParams); } return $link; } public function getEncodeAmpersands() { return $this->encodeAmpersands; } public function setEncodeAmpersands($encodeAmpersands) { $this->encodeAmpersands = $encodeAmpersands; } }

Please note, that this implementation is only an example since it has the following limitations:

  • Actions that have specified $keepInUrl = true are not automatically included in the generated url.
  • Due to simplicity we have not included any checks on the page parameter.
  • Scheme, host, port, and path of the url abstraction are ignored.
  • The link scheme is only usable for "normal" urls but has currently no corresponding input filter to resolve this layout.

In order to configure existing or custom link schemes for global use you can adapt your bootstrap file as follows:

PHP code
// globale configuration using an existing link scheme include('../apps/core/pagecontroller/pagecontroller.php'); import('tools::link', 'LinkGenerator'); LinkGenerator::setLinkScheme(new RewriteLinkScheme(true)); // globale configuration using a custom link scheme include('../apps/core/pagecontroller/pagecontroller.php'); import('tools::link', 'LinkGenerator'); import('...', 'SpecialLinkScheme'); LinkGenerator::setLinkScheme(new SpecialLinkScheme());

With no special configuration present within the bootstrap file the DefaultLinkScheme will be used as long as the URLRewriting registry key from the apf::core namespace contains the value false. If you have activated url rewriting through the registry the RewriteLinkScheme is used.

Comments

Do you want to add a comment to the article above, or do you want to post additional hints? So please click here. Comments already posted can be found below.
There are no comments belonging to this article.