Introduction
Themes enhances any application by providing a common feature set that most developers and frontend designers patchwork together. A common problem associated with the presentation layer of application design is often directly associated with the workflow of your team. Providing a standard that can be looked to day in and day out is essential to lowering team confusion and overhead.
The package requires PHP 8.0+ and follows the FIG standard PSR-4 to ensure a high level of interoperability between shared PHP code and is fully unit-tested.
Have a read through the Installation Guide and on how to Integrate it with Laravel 9.
Concepts
When developing themes, we set out to tackle a common problem associated with teams. There are often two areas of responsibility when creating your application:
- Developers
- Designers
We love creating packages which are both developer-centric as well as designer-friendly. Separation of concerns is a great strategy.
Backend developers aren't great with design (because let's face it, backend developers would rather just "Twitter Bootstrap" every site they work on).
Frontend designers don't want to tackle with complex PHP code.
For this reason, we wanted to keep all asset handling within views and not in controllers or configuration files.
Features
Our themes package enhances any application while allowing the freedom to organize and maintain your application views as you see fit. Themes currently supports the following feature set.
- Any number of theme locations.
- Support for theme "areas" (such as "backend" or "frontend" themes, you can choose anything).
- Unlimited theme inheritence; you can make an unlimited chain of themes which inherit off another theme. Views and assets cascade throughout theme inheritence.
- Fallback theme support; nominate a theme which views and assets fallback to if they cannot be found in the active theme hierarchy.
- Asset compilation (LESS, SASS, SCSS, CoffeeScript etc), minification and compression into one asset (configurable per environment).
- Powerful, dynamically generated static asset cache; assets are cached when compiled to static files, which is blazingly fast. We don't serve assets through frameworks/controllers as this adds significant overhead.
- Theme publishing (publish your own packages / extensions with support for any theme in them and publish them from within the Artisan CLI in Laravel 9). Of course, this can work outside of Laravel as well.
Setup
Installation
Cartalyst packages utilize Composer, for more information on how to install Composer please read the Composer Documentation.
Preparation
Open your composer.json
file and add the following to the require
array:
"cartalyst/themes": "^8.0"
Add the following lines after the require
array on your composer.json
file:
"repositories": [
{ "type": "composer", "url": "https://packages.cartalyst.com" }
]
Note: Make sure that after the required changes your
composer.json
file is valid by runningcomposer validate
.
Install the dependencies
Run the composer install
or composer update
to install or update the new requirement.
Integration
Laravel 9
The Themes package has optional support for Laravel 9 and it comes bundled with a Service Provider and Facades for easy integration.
After installing the package, open your Laravel config file located at app/config/app.php
and add the following lines.
In the $providers
array add the following service provider for this package.
Cartalyst\Themes\ThemesServiceProvider::class,
In the $aliases
array add the following facades for this package.
'Theme' => Cartalyst\Themes\Facades\Theme::class,
'Asset' => Cartalyst\Themes\Facades\Asset::class,
Resources
Configuration
To publish the configuration file run the following on your terminal:
php artisan vendor:publish --provider="Cartalyst\Themes\ThemesServiceProvider" --tag="config"
This will publish the config file to config/cartalyst/themes/config.php
where you can modify the package configuration.
Note 1 Once you publish themes, you will need to configure the theme paths as they were relative to the config file before it was published.
Note 2 Assets are compiled to a directory relative to your public directory. By defaults this is
public/assets/cache
(wherepublic
is the public directory). Please ensure this folder exists and is writeable by your web server.Note 3 By default we include a number of filters which are applied to assets with a given file extension. The packages are not required to make themes run and therefore are not installed, bug suggested. See Installing for a reference for the packages you should install to work with the default filters. Of course, feel free to change or remove these, we just think they're good defaults.
Usage
Selecting the active theme
Selecting the active theme is very easy. Themes have slugs which identify them and by default the slug should match the path to the theme. How does this work?
Let's say your themes are located under public/themes
. If you wanted a theme with a slug foo/bar
, it should reside under public/themes/foo/bar
. A theme with a slug default
should reside under public/themes/default
. It's that simple.
We have added the concept of theme "areas". Say you were building an eCommerce website, you may wish to have themes for the "frontend" of your website as well as the "backend".
How do we do this? It's actually pretty much the same. Let's say we have a theme with backend::foo/bar
. It should be under public/themes/backend/foo/bar
. Think of areas as namespacing.
To set the active theme, ensure it's folder structure exists as well as a theme.json
file (see below) and just put it's slug as the active
theme in your configuration.
Setting the fallback theme is the exact same process except you are entering the fallback
theme in your configuration.
theme.json
Each theme should have a theme.json
file in it. It should be the following structure:
{
"slug": "frontend::foo/bar",
"parent": "frontend::base",
"name": "Foo-Bar theme",
"author": "Cartalyst LLC",
"description": "The best theme you've ever seen.",
"version": "1.0.0"
}
Your theme.json
contains the following attributes:
- Slug - the slug of the theme. It should match the folder structure the theme resides in and is used to validate the theme package has found the right theme.
- Parent - the slug of the parent of this theme. Any views / assets which cannot be found in this theme will cascade to that parent theme. Not required.
- Name - A human friendly name of the theme. Not required.
- Author - The author of the theme (person, nickname, company etc). Not required.
- Description - A description of the theme. Not required.
- Version - The theme's version. Not required.
Debug
There is a "debug" option in your configuration. When themes is in debug mode, assets are compiled (as per the selected filters) however are kept separate. When debug mode is turned off, all style assets are compiled into one file as well as all script assets. This greatly reduces the number of HTTP requests made to the server by users and speeds up the application.
This value is set to null
at first, meaning if your application is running in the production
environment, it will automatically turn debug off. You can explicitly set it to true
or false
.
Theme Structure
Each theme has two different main directories:
- Views - contains the views for the theme.
- Assets - contains the assets for the theme.
A theme may have global views and assets, as well as sectioned views and assets. The sections which themes have are:
- Packages - contains views and assets for a package.
- Namespaces - contains views and assets for a "namespace", which is a predefined location for views (such as pagination views). We allow theming of namespaced views.
How would this structure work? Let's say we have a theme with views for a package named foo/bar
. Additionally, we are also theming pagination
views. Our theme could look like:
assets
| css
| | style.css
| less
| | bootstrap.less
| js
| | jquery.js
namespaces
| pagination
| | assets
| | | paginator.css
| | views
| | | paginator.blade.php
packages
| foo
| | bar
| | | assets
| | | | js
| | | | | bar.js
| | | views
| | | | partials
| | | | | table.blade.php
views
| home.blade.php
| signup.php
As you can see, each theme has two directories, assets
and views
. It also has packages
and namespaces
, each of which has the corresponding package or namespace and then their own assets
and views
directories.
Views
By default, Themes ships with view support for illuminate/view which is the package that drives Laravel 7 views. Our demos and documenation will use this package's view system.
Once you've set the active theme, view loading is the same as it always was:
View::make('foo/bar', $data);
What will happen here is:
- The theme system will look for
views/theme/bar.blade.php
in the active theme and all of its parents, followed by the fallback theme if it is set. - If it can find it, it will render it. If it can't, it will fallback to
illuminate/view
to find it. - If nothing finds it, an exception is thrown as normal.
Namespaces & Packages
Loading namespaced and package views is done using the same syntax you are already familiar with for loading namespaced views:
View::make('vendor/package::view/name', $data);
View::make('namespace::view/name', $data);
If you're loading a namespace view, the theme system will attempt to find the view in the namespaces
directory (in this case namespaces/namespace/views/view/name.blade.php
) in the active theme. The rest is the same as normal view loading above.
If you're loading a package view, the theme system will attempt to find the view in the packages
directory (in this case packages/vendor/package/views/view/name.blade.php
) in the active theme and all of its parents, followed by the fallback theme if it is set. If it cannot be found, an exception will be thrown (as there is no native view support for packages).
Easy, huh?
Usage
Ensure you have an instance of Cartalyst\Themes\Assets\AssetManager setup. If you've followed our Laravel 9 installation guide, this is already done for you and is accessible through the static Asset
methods. We'll be using this facade in this article to keep the syntax cleaner.
Queuing assets
Queuing assets is very easy in our Themes package:
// Queue a CSS asset called "style".
Asset::queue('style', 'css/style.css');
// Queue a LESS asset called "custom", which relies
// on "style" being loaded first
Asset::queue('custom', 'namespace::css/custom.less', 'style');
// Queue jQuery
Asset::queue('jquery', 'js/jquery-1.9.1.min.js');
// And bootstrap
Asset::queue('bootstrap', 'vendor/package::js/bootstrap.js', 'jquery');
// And our custom JS
Asset::queue('script', 'js/script.js', array('bootstrap', 'jquery'));
As you can see, the arguments are:
- Alias - This is a friendly name of the asset. For example, 'jquery'. It may refer to a physical JavaScript file with a version. When you refer to it however, you can just use 'jquery'. If the path of the physical file changes, nothing needs to change except for where you register the style.
- Path - This is the path to the asset. This is resolved to the
assets
folder in a theme or a theme'snamespaces
orpackages
path. This works automatically. - Dependencies - This is a string or array of aliases for the dependencies for this asset. This may be required if you are loading assets in partial views, as some rendering engines (such as Blade) will load the partial views before the main view, meaning assets are not queued in the order you expect. Rule of thumb, supply dependencies and everything will work beautifully!
Compiling assets
Compiling assets is even simpler than queuing them:
$urls = Asset::compileStyles();
$urls = Asset::compileScripts();
In the code above, $urls
will be an array of URLs to the compiled assets. If you are in debug
mode, this array will only have 1 entry for styles, one for scripts. If your'e not, you will have multiple entries. Iterating through these urls is the best way to output them:
foreach (Asset::compiledStyles() as $url) echo $url;
Of course, all of the above code can be adapted for Laravel's Blade templating engine:
{{ Asset::queue('style', 'css/style.css') }}
@foreach (Asset::getCompiledStyles() as $style)
<link href="{{ $style }}" rel="stylesheet">
@endforeach
Filters
Our themes package makes use of Assetic, a great asset management package for PHP. It makes use of filters (for which there are many tutorials available).
Our themes package simplifies asset handling by applying filters across all assets with a given extension. We are looking towards supporting filters per-asset in a future version of our Themes package as well for even further flexibility.
When configuring themes you may specify any filters which automatically get applied to assets with a given file extension.
Simple, no bloat. That's the way we love it.
Publishing Themes
When we outlined our philosophy for creating themes, you may have noticed that we believe in a separation of themes from backend code.
This is all "good and well", but what happens if you want to distribute a package or extension with theme support in it? An example of this is if somebody releases an extension for our Cartalyst eCommerce Platform (in the works) and wants to include files for the default "Cartalyst eCommerce" themes within the extension.
The answer?
Theme publishing.
We have included a ThemePublisher
class which allows you to publish the theme files from a given source to any valid themes. It's automatic and it just works.
All you have to do is include a themes/
folder inside a composer packge or Cartalyst Extension, and the correct folder structure and valid folders will be copied across.
Let's say we have the following themes in our application:
backend::default
frontend::default
If we have a package with views and assets for this theme, we could have the following file structure:
themes
| backend
| | default
| | | assets
| | | | css
| | | | | foo.css
| | | packages
| | | | packagename
| | | | | views
| | | | | | register.blade.php
You get the point. Just include the path to the theme (with the theme's area if it has one) and the files in the correct structure. Then, call ThemePublisher::publishPackage('packagename');
or ThemePublisher::publishExtension($extension);
.
We've integrated the theme publisher with the Artisan CLI:
php artisan theme:publish --package=foo/bar
php artisan theme:publish --extension=baz/bat
php artisan theme:publish --extensions
The --watch
switch may be added to any of the above commands and is particuarly useful during development. Using --watch
will cause theme assets to be published, automatically, as soon as they are modified on the filesystem. This saves you the trouble of having to execute php artisan theme:publish --extension=baz/bat
every time you modify a theme asset and wish to see the changes reflected in your project. Ultimately, the --watch
switch simply causes theme assets to be copied from one or more extensions into the default theme whenever changes are detected.
When you're done with --watch
, issue the standard terminal abort command (Ctrl
+ C
on most keyboards) to stop watching.
Enjoy!