Everyone uses font icons these days.
The most common, most powerful library seems to be FontAwesome. But also BootstrapIcons is used quite a bit.
Not so common yet are Material and Feather as icon libraries. Some of those also use SVGs or sprites.
Should we write a PHP helper class? Or should we deal with them inside templates manually?
In the following post we want to see how we can tackle some of the issues around such icons in a DRY and future-proof way from within (Cake)PHP:
- Abstraction: Icons are often named contrary to what we would search for (or the intention we want to use them for)
- Usability: Icons need to be looked up remotely or manually typed out
- Flexibility: We need full control over the generated markup and make it easy to use from PHP/user side
For that, we will look into the CakePHP helper called Icon
from the Tools plugin.
It provides us an API as follows:
echo $this->Icon->render($iconName);
Docs: github.com/dereuromark/cakephp-tools/blob/master/docs/Helper/Icon.md
Demo: sandbox.dereuromark.de/sandbox/tools-examples/icons
Abstraction
You want to add an icon for details using FontAwesome. Looking for this or aliases yields no results in the icon overview on their website.
But after some intense looking, you find chevron-right
as a suitable one.
After some more time has passed, you forgot again the too-concrete name for the abstract action we want to use it for.
What if we use aliasing here?
'Icon' => [
'map' => [
'details' => 'fa:chevron-right',
'admin' => 'fa:shield',
'login' => 'fa:sign-in',
'logout' => 'fa:sign-out',
'translate' => 'fa:language',
'prev' => 'fa:arrow-left',
'next' => 'fa:arrow-right',
],
],
Now you can just use
echo $this->Icon->render('details');
UX
Having to manually type, refresh the page and see if the icon actually exists is cumbersome.
Could we make this autocompleted?
Turns out, with meta data and an IDE like PHPStorm you can do that.
We just have to write a collector that reads the icon font file(s) and gives us an array of available icons, then feed this into the meta data.
For this we can use the same icon class now and IdeHelperExtra to process it for our IdeHelper plugin.
We just add the task here into our config:
'generatorTasks' => [
\IdeHelperExtra\Tools\Generator\Task\IconRenderTask::class,
],
It (re)uses the configuration of your Icon helper, so should work out of the box.
The generated autocomplete file should give us now the list of icons as dropdown in the IDE:
There is yet another benefit of using the helper wrapper and aliasing using the map above:
You can get auto-added title attribute for free explaining the meaning on mouse over or for screen readers.
The output of the admin link $this->Icon->render('admin')
would be (in FA5):
<span class="fas fa-shield-alt" title="Admin"></span>
By default, it would also be auto-translated for you.
You can of course always manually customize it more if needed.
Flexibility
The Icon Helper provides a way to combine several font icons under the same hood, allowing them to be used simultaneously.
You can also customize one Icon class, or add a completely new or custom (font) icon set this way, making it super flexible for every use case.
If we want to use BootstrapIcons, FontAwesome6, Materials and Feather together, it would look something like this:
'Icon' => [
'sets' => [
'fas' => [
'class' => \Tools\View\Icon\FontAwesome6Icon::class,
],
'bs' => [
'class' => \Tools\View\Icon\BootstrapIcon::class,
],
],
],
From the user side, you wouldn’t even need to know where mapped icons come from, they would just work out of the box.
And exchanging the Icon class or upgrading the font should ideally be abstracted away so that nothing changes for your template code.
Template engines
You can also change the template engine from plain PHP to Twig other others.
The helper is just a proxy, it was written on purpose to contain all logic inside the collection:
public function render(string $icon, array $options = [], array $attributes = []) {
return $this->collection->render($icon, $options, $attributes);
}
So any wrapper could do the same, forwarding to ItemCollection::render()
then. E.g. your Twig plugin.
Summary
We could show that having an abstraction here could provide helpful both for development, but also for maintenance.
You can fast-type using auto-complete, you can alias to abstract and make it more aligned with what they are used for.
But we also can easily exchange the whole rendering via templating if needed.
Enjoy the Helper if you find it useful for your project(s)!
Hi Mark,
i think you have used the "local env link" for the demo :
http://sandbox.local/sandbox/tools-examples/icons
Thx! Fixed 🙂