WP4Laravel is by default a standard Laravel project. Instead of using a relational database it uses WordPress as Content Management System., (*1)
The benefits relative to a standard WordPress Setup:, (*2)
Use MVC-principles
Better performance
Flexibility
Sustainability
Security
The benefits relative to a standard Laravel project:, (*3)
No need to create a custom CMS
Get the best of great WordPress plugins
For commercial purposes, you can sell the customer a WordPress CMS.
Dependencies
The basis of WP4Laravel is just a fresh Laravel install. We add three open source projects in the mix:
* WordPress as a dependency (https://github.com/johnpbloch/wordpress)
* Corcel: Get WordPress data with Eloquent (https://github.com/corcel/corcel)
* Wordplate: Standard theme and plugin for WordPress (only for inspiration, not actually installed), (*4)
Installation
Start a fresh Laravel install: https://laravel.com/docs/10.x/installation, (*5)
To use WordPress as a dependency, you need to extend your composer.json. Add a repositories section to the composer.json and ad the following repositories:, (*7)
Open the app/corcel.php config file and define your database connection., (*14)
If you're using Laravel 5.4 or earlier, you need to configure the CorcelServiceProvider. Add the following line to your config/app.php under "Package Service Providers":, (*15)
Corcel\Laravel\CorcelServiceProvider::class,
Publish public data
Unfortunately, the base theme and config of WordPress has to be inside the webroot. You can publish these from WP4LaravelServiceProvider., (*16)
All WordPress media will be saved in the location of the Laravel Public storage. To make this work, run the following Artisan command to make a symbolic link in your webroot., (*17)
php artisan storage:link
Remove unused migrations
If you're only using tables managed by WordPress, it's recommended to remove the default migrations generated by Laravel. These files are in database/migrations/., (*18)
Install WordPress
Go to /wp/wp-admin to setup your WordPress project., (*19)
Replace the 'Laravel' heading in resources/views/welcome.blade.php by {{ $post->title }}., (*21)
Open your project in the browser, you will see 'Hello World!' as heading., (*22)
References
Read the docs of Corcel before you start: https://github.com/corcel/corcel
Advanced Custom Fields
What makes WordPress a real CMS? Right: Advanced Custom Fields. To implement this concept we use 2 packages, (*23)
The Advanced Custom Fields WordPress plugin
The Corcel ACF plugin to fetch ACF data from a Corcel / Eloquent model.
The Corcel ACF plugin is a direct dependency of WP4Laravel and is automatically installed. To get the ACF 5 PRO plugin in WordPress using composer, follow the instructions on https://github.com/PhilippBaschke/acf-pro-installer. Alternatively, if you don't have a license for ACF Pro, you can use the free version:, (*24)
Look at the docs of Corcel (https://github.com/corcel/acf) for the usage of Corcel with ACF., (*25)
Wordpress configuration
Within /public/themes/wp4laravel/library you can update the WordPress configuration. Most used for configuring post types and taxonomies. Every post type kan be defined in the directory post types. En example is already included. For taxonomies, it works the same., (*26)
If you want to define your post types and taxonomies with a WordPress plugin, that's no problem., (*27)
Add plugins
Because WordPress and his plugins are dependencies, you can only use plugins which are available with composer., (*28)
WordPress Packagist comes straight out of the box with WP4Laravel. It mirrors the WordPress plugin as a Composer repository., (*29)
How do I use it?
Require the desired plugin using wpackagist-plugin as the vendor name., (*30)
Please visit WordPress Packagist website for more information and examples., (*32)
Yoast Premium
WP4Laravel has some support for key features of Yoast Premium, mostly rendering SEO tags and following redirects., (*33)
Redirects
Redirect can be enabled by adding the included middleware to the middleware-stack. You probably want to do this in the web middleware group in app/Http/Kernel.php:, (*34)
The middleware will automatically check if the request should be redirected, and return the redirect instead of handling the original request. Note that the redirect is handled in middleware and takes precedence over any existing routing. Take care not to define redirects over critical pages in your website., (*35)
Multilanguage
WP4Laravel contains various options to work with multilanguage-enabled websites. These solutions are based on using the free version of Poylang (plugin, wpackagist)., (*36)
Translatable models
A Translatable trait is included for working with the Polylang plugin. Include this trait in your models to gain access to useful properties for working with translated versions of posts., (*37)
class Post extends \Corcel\Post
{
use \WP4Laravel\Multilanguage\Translatable;
}
Including the trait with add a language scope for use with Eloquent and a language property., (*38)
$posts = Post::language('de')->published()->first();
echo $post->language; // de
It also includes a translations property which yields a collection, keyed by the language code, of all available translations of a given post., (*39)
$post = Post::slug('about-us')->first();
echo $post->translations['nl']->title; // Over ons
Translatable taxonomies
Similarly to translating models, WP4Laravel also supports translating taxonomies. For this, you must enable the taxonomy to be translated in WP-Admin > Languages > Settings > Custom post Types and Taxonomies. To use the translation information on the website, create a model for the taxonomy and add the TranslatableTaxonomy-trait:, (*40)
class EventType extends \Corcel\Model\Taxonomy
{
use \WP4Laravel\Multilanguage\TranslatableTaxonomy;
protected $taxonomy = 'event_type';
}
The trait creates a scope on the model language(string) which can be used to filter terms:, (*41)
$language = localization()->getCurrentLocale();
$eventTypes = EventType::language()->get(); // Returns only the
Making translatable menu's
The MenuBuilder has a utility function to work with menu's that have been translated using Polylang. First, configure your theme to have various menu locations. These are the slots on your website in which a menu is going to be displayed. Each entry has a location identifier and description:, (*42)
register_nav_menu('main', 'Main navigation in header');
register_nav_menu('contact', 'Contact links in menu dropdown and footer');
register_nav_menu('footer', 'Additional footer links');
Polylang will automatically make translated locations for every language you specify. Use the Wordpress admin interface to create a menu and assign it to a location. Than, call the MenuBuilder::menuForLocation($slot, $language) method call to find the appropriate menu for a location. It returns a basic Corcel\Model\Menu class. This method supports both translated and untranslated menu structures., (*43)
// Get a untranslated menu
$menu = MenuBuilder::menuForLocation('main');
// Get a translated menu for a location
$menu = MenuBuilder::menuForLocation('main', Localization::getCurrentLanguage());
Best practices
Create your own models for each post type
If you want to take advantage of the power of Eloquent, we advise to create a Laravel model for each of your post types., (*44)
namespace App\Models;
use Corcel\Post as Corcel;
class Event extends Corcel
{
protected $postType = 'event';
}
For example, you can add accessors to make life easier., (*45)
Register your post types
When you access a post type from a specific model, you have to register this. You can do this to match a post_type with the dedicated model in the config/corcel.php file. For example:, (*46)
Create a PageController with the show method inside:, (*51)
PageController:, (*52)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Page;
/**
* Catch all routes which are not defined in the routes file
* Next search for a page which has the same url structure as the route
* If not found,
*/
class PageController extends Controller
{
public function show($url)
{
// Get the post by url or abort
$post = Page::url($url);
// Add post data to the site container
app('site')->model($post);
// Show the template which is possibly chosen in WP
return view($post->template);
}
}
Page Model:, (*53)
<?php
namespace App\Models;
use WP4Laravel\Corcel\Pageurl;
/*
* Model for WP pages
*/
class Page extends Post
{
// The Pageurl trait has a method to find a page based on the full url.
use Pageurl;
/**
* What is the WP post type for this model?
* @var string
*/
protected $postType = 'page';
}
Setup your homepage
Note: This feature is available since version 0.7, (*54)
Use a specific controller for your homepage or use the index method in your PageController for the homepage. Select in WordPress in Settings/Reading the page which you want to use as Homepage., (*55)
namespace App\Http\Controllers;
use App\Models\Page;
class HomeController extends Controller {
public function __invoke()
{
/*
* The Pageurl trait includes a homepage method wich get the ID of the page from the WP_options table
*/
$post = Page::homepage();
return view('home', compact('post'));
}
}
Get the url of a page
Note: This feature is available since version 0.7, (*56)
The Pageurl trait includes a getUrlAttribute method which generated the url of a page. This will include the path if a page has a parent., (*57)
$page->url;
Rendering <picture> tags
WP4Laravel includes a helper template and ViewProvider to correctly render <picture>-tags with crops, etc. This works correctly for both ThumbnailMeta and Image-classes., (*58)
Configuration
The included configuration file config/picture.php can be adapted to your project configuration. Copy the file to your project by executing:, (*59)
Note that using multiple <source>-tags requires the use of media queries, so while the following example will generate output and not crash, the W3 Validator will throw an error on your generated HTML. Don't do this., (*65)
Images are complex objects in the WordPress/Corcel-environment, and mocking them is not trivial. This presents problems: if you design a component that renders some HTML and a inner image using WP4Laravel\Picture, you need to create a pretty complex structure to have all the necessary fields available., (*66)
For this use case, we offer a fake class: WP4Laravel\ImageFake. This fake class presents the necessary fields and methods to appear as valid option to WP4Laravel\Picture and can be used in the styleguide to render components that contain images. Note that ImageFake is only suitable for use with Picture, as it is a partial fake., (*67)
An example: suppose you have a component named "pretty_picture" that renders a single image + figure and caption. The component HTML looks like this:, (*68)
You can use this component on the website as such, where $block->fields->image is an instance of either ThumbnailMeta or Image, and $captionText is a string:, (*69)
The alt-text is optional. ImageFake requires that you always specify at least the 'full' option, as that is the case for real images too. The minimum data you'll need to add is thus:, (*71)
WP4Laravel supplies a MenuBuilder utility class that can calculate the correct menu for you. You can use the class in a ViewComposer for example. This class correctly deals with using the custom title of a menu item or the post title when none is set., (*72)
The MenuBuilder class has a single public method itemsIn($menu) which returns a Collection of top-level items in the menu. Each entry has an id (int), title (string), active (whether this item should be "selected", boolean) and url (string) property. Additionally, each item has a target property (boolean). If set, you should open the link in a new tab. Make sure to set rel=noopener too to prevent cross-site scripting and performance problems., (*73)
This class supports a single level of nesting (two levels in total). Root-level items have a children (Collection) property with a list of their immediate child entries. Additionally, a root-level item has a boolean childActive property which is true if any of its children have the active flag set., (*74)
The MenuBuilder requires that your model has a url property that contains the canonical URL of an instance of the model., (*75)
Example usage
Add a URL property on your model. For example, when using a custom slug in the URL and a multilanguage-based setup:, (*76)
use App\Models\Traits\Translatable;
class Post extends \Corcel\Post
{
protected $postType = 'post';
protected $urlScope = 'nieuws';
/**
* Full URL to a post object
* @return string
*/
public function getUrlAttribute()
{
$url = '/' . $this->slug;
// Prepend URL scope
if (!empty($this->urlScope)) {
$url = '/' . $this->urlScope . $url;
}
// Prepend the language if it's not the default language
if (!empty($this->language) && $this->language !== config('app.supported-locales')[0]) {
$url = '/' . $this->language . $url;
}
return $url;
}
}
<?php
namespace App\Http\ViewComposers;
use Corcel\Menu;
use Illuminate\Http\Request;
use Illuminate\View\View;
use WP4Laravel\MenuBuilder;
class Navigation
{
private $builder;
public function __construct(MenuBuilder $builder)
{
$this->builder = $builder;
}
public function compose(View $view)
{
$menu = Menu::slug('main-menu')->first();
$view->with('menu', $this->builder->itemsIn($menu));
}
}
Alternatively, you can use the MenuBuilder-facade to gain a static interface:, (*79)
use WP4Laravel\Facades\MenuBuilder;
MenuBuilder::all();
When you want to preview a post which is not yet published, the Wordpress endpoint for the preview is your homepage added with some GET parameters. To catch these parameters you have to append a specific middleware to your homepage route. When the middleware matches a preview, it will redirect to the defined route of the post type, like 'blog.show'. As slug will be used "__preview".
Note: You are free to implement the middleware on your own way, more info at the Laravel Docs, (*83)
Append the Preview trait to you dedicated models:
namespace App\Models;
use WP4Laravel\Corcel\Preview;
class Post extends \Corcel\Model\Post {
use Preview;
}
Make sure each model has a static method called current. This method will be used to select the current post based on the url. In most cases this method looks like:
public static function current($slug)
{
return static::published()
->slug($url)
->firstOrFail();
}
Excepts for a page which can have a parent, use the following if your Page models uses the Pageurl trait., (*84)
public static function current($url)
{
return static::url($url);
}
In your controller use the static method publishedOrPreview method to get your current post or a preview.
public function show(Request $request, $slug)
{
$post = Post::publishedOrPreview($request, $slug);
return view($post->template, compact('post'));
}
SEO tags for models
Creating the right SEO-tags depends on defining what the "primary" model instance is of this page. On the page of a news item, this is likely the news item itself. For an index page, you might want to create a specific page (with or without content) just so that you have place to configure Yoast., (*85)
The primary instance for a site is set on the Site. You usually do this in every controller action that renders a page., (*86)
This trait adds a seo-attribute $post->seo which contains an array of all meta keys and their appropriate values. You can render all appropriate properties using:, (*88)
The Corcel libraries doesn't support media posts from external storage like an S3 bucket. This wrapper adds this support to get url's of the original files but also the url's of the generated thumbnails., (*89)
Requirements
Laravel configured with S3 storage
WordPress configured with the S3 Offload plugin
Usage
Get the url of the featured image of a post, (*90)
Because of the main usage in a blade template, the S3Media object does not generate exceptions. If something is wrong (bad input, file not exists) the url() and site() methods just returns null., (*94)
RSS-feeds
This feature requires WP4Laravel 0.8.0 or later, (*95)
WP4Laravel has built-in rudimentary support for generating RSS-feeds. Use as follows:, (*96)
The first argument is a (Eloquent) Collection of all items you want included in your feed (note: these items must be
instances or subclasses of Corcel\Model\Post). The second parameter is the title included of the feed., (*97)