Editors note: This post was originally published in July 2015, but a lot has changed since then. Laravel has been upgraded to version 5.4 (it was 5.1 at the time of writing), and Wordpress is now up to version 4.7, and has integrated the WP-API plugin into its core. I’ve updated to article to reflect those changes.
Recently I was tasked with building an API with a Wordpress backend, to which some would say: just use the default REST API. Now, that’s perfectly reasonable, but what if you want to use custom post types, and not use the JSON structure Wordpress has decided upon? And what if you want better performance? My answer: Laravel + Wordpress.
If you’ve already tried to make one of these solutions, you’ll probably know that it’s not as easy as it sounds. Heck, that might even be why you’re here!
My solution is based on an environment where you have no say as to where your virtual host is directed (Laravel’s public-folder being the usual suspect), and using Laravel 5.4.
If you feel comfortable looking through Laravel and Wordpress code and figuring out how it all works, I’ve created a Github repository with a demo project that follows these guidelines.
I place Wordpress in the base directory of my site, and Laravel in a subfolder called “api”, leaving me with a structure like this:
/
- api/
# Laravel files
- app/
- bootstrap/
- config/
...
# Wordpress files
- wp-admin/
- wp-content/
- wp-includes/
...
Now that the files have been unpacked and structured, we’re off to code country!
Laravel
There are basically three things you need to change to make Laravel work:
The database config
Add a base .htaccess file
The public index.php file
Database config
To change the database config is pretty much a given. You need to change api/config/database.php
to reflect the Wordpress influence. The biggest difference from the normal behavior, is that the config file should use the variables defined in the wp-config.php
file (including the $table_prefix
global variable), like this:
<?php
global $table_prefix;
return [
...
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'database' => storage_path('database.sqlite'),
'prefix' => '',
],
'mysql' => [
'driver' => 'mysql',
'host' => DB_HOST,
'port' => env('DB_PORT', '3306'),
'database' => DB_NAME,
'username' => DB_USER,
'password' => DB_PASSWORD,
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => $table_prefix,
'strict' => true,
'engine' => null,
],
...
Now the Laravel installation is listening directly to Wordpress’ configuration file.
.htaccess
The second thing you need to do, is add a base .htaccess file to the Laravel installation. You need to do this because Laravel is in a subdirectory, and needs all requests sent to the public folder. In your “api”-folder, add a .htaccess file with the following content:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^(.*)$ public/$1 [L]
</IfModule>
Include Wordpress
The last thing you need, is to include Wordpress in Laravel’s public index.php file (api/public/index.php
). To do this, just add the following lines at the top of the file:
define('WP_USE_THEMES', false);
require __DIR__.'/../../wp-blog-header.php';
That’s basically what’s needed for Laravel to work in a Wordpress subdirectory.
Wordpress
Besides setting up wordpress to work with your database, you need to add some code. I know, it sucks. But it’s only one thing, well, one theme. In you theme directory (wp-content/themes
) add a folder. I’ve called mine “laravel”. Now you just to add three small files:
functions.php
index.php
style.css
These files are needed for Wordpress to understand that this is a theme.
style.css
This is just basic setup for a theme, nothing fancy here.
/*
Theme Name: API Theme
Author: CMAndersen
Author URI: https://cma.dk
Version: 1.0
*/
index.php
This code just redirects the user to the backend, since we don’t need a frontend for our API.
<?php
// Redirect the user to the admin panel
header('Location: ' . admin_url());
die;
functions.php
This file is more serious. I spent hours figuring this one out.
Since Wordpress is our default access to the site, it will add a 404 header when we access the API. Not only that, but Laravel will crash from the added headers.
<?php
// Remove the headers, since Laravel sets it's own headers, and
// Laravel is more important for us.
add_filter('wp_headers', 'api_theme_header_removal');
function api_theme_header_removal($headers) {
return [];
}
Now you can access both the Wordpress and the Laravel installation.
Of course you need to set up Laravel to actually be able to read the Wordpress data structure. I’ve been working on a trait that acts as a transformation layer between the Wordpress database structure and a normal database structure:
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Builder;
trait Wordpress
{
/**
* Array of items that should be appended
*
* @var array
*/
protected $transformedAttributes = [
'id',
'title',
'slug',
'content',
'url',
'created_at',
'updated_at',
];
/**
* Override the Model method to apply some default scopes
*
* @return mixed
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('post_type', function (Builder $builder) {
$variables = get_class_vars(get_called_class());
$builder
->where('post_type', $variables['post_type'])
->where('post_status', 'publish');
});
}
/**
* Translate the ID attribute to lowercase
*
* @return mixed
*/
public function getIdAttribute()
{
return $this->attributes['ID'];
}
/**
* Transform the post_title attribute
*
* @return mixed
*/
public function getTitleAttribute()
{
return $this->attributes['post_title'];
}
/**
* Transform the post_name attribute
*
* @return mixed
*/
public function getSlugAttribute()
{
return $this->attributes['post_name'];
}
/**
* Transform the post_content attribute
*
* @return mixed
*/
public function getContentAttribute()
{
return apply_filters('the_content', $this->attributes['post_content']);
}
/**
* Display the posts url
*
* @return string
*/
public function getUrlAttribute()
{
return get_permalink($this->attributes['ID']);
}
/**
* Transform the post_date_gmt attribute
*
* @return mixed
*/
public function getCreatedAtAttribute()
{
return $this->attributes['post_date_gmt'];
}
/**
* Transform the post_modified_gmt attribute
*
* @return mixed
*/
public function getUpdatedAtAttribute()
{
return $this->attributes['post_modified_gmt'];
}
/**
* Override the Model method to supply the Wordpress ID
*
* @return string
*/
public function getKeyName()
{
return $this->primaryKey ? $this->primaryKey : 'ID';
}
/**
* Override the Model method to only show the transformed attributes
*
* @return array
*/
public function getVisible()
{
return array_merge($this->visible, $this->transformedAttributes);
}
/**
* Override the Model method to automatically append our transformed attributes
*
* @return mixed
*/
protected function getArrayableAppends()
{
$appends = array_merge($this->transformedAttributes, $this->appends);
return $this->getArrayableItems(
array_combine($appends, $appends)
);
}
}
To use this trait you simply include it in a model (like Post or Page), and add a few properties to the model:
protected $table = 'posts';
protected static $post_type = 'post' // Or any other post type;