Supercharging WordPress
Development in 2018
by Adam Tomat, Rareloop
LUMBERJACK
@adamtomat
The Digital Product Studio
• PHP-FIG and PSRs
• The “problem”
• What’s available in Lumberjack
• Controllers
• Config
• Post Objects
• Query Builder
• Routing
• Responses
• Collections
• Other shiny things in Lumberjack
• On the radar
What We’ll Cover…
PHP-FIG
&

PSRs
PHP Framework Interop Group
PHP Standards Recommendations
https://www.php-fig.org/psr
PSR11 - PSR7 - PSR15 - PSR3
What problem are we solving?
CONTENT SITE BESPOKE SYSTEM
Project Spectrum
Lumberjack 1
Aimed to make development more sane, came up with some conventions.
Stopped short of being a full framework.
Built on two open source projects
Launched end of 2015
What is Timber?
index.php
<h1><?php echo get_post_meta(get_the_ID(), 'customTitle'); ?></h1>
<p><?php echo get_post_meta(get_the_ID(), 'customDescription'); ?></p>
<?php if (have_posts()) : while (have_posts()) : the_post(); ?>
<div id="post-<?php the_ID(); ?>">
<h1><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h1>
<?php the_content(); ?>
</div>
<?php endwhile; ?>
<h1>{{ customTitle }}</h1>
<p>{{ customDescription }}</p>
{% for post in posts %}
<div id="post-{{ post->id }}">
<h2><a href="{{ post->permalink }}">{{ post->title }}</a></h2>
{{ post->content|raw }}
</div>
{% endfor %}
index.twig
$context = Timber::get_context();
// Get custom meta from current page
$context['customTitle'] = get_post_meta(the_ID(), 'customTitle');
$context['customDescription'] = get_post_meta(the_ID(), 'customDescription');
// Get list of posts to show
$context['posts'] = Timber::get_posts();
Timber::render('index.twig', $context);
index.php
What is Bedrock?
Traditional WordPress
Theme
WordPressTimber
Yoast
ACF
Theme
Lumberjack 1
Bedrock
WordPress v4.5.*
Timber
v1.1.*
Yoast
v5.2.3
ACF
v5.6.1.2
Theme
The new Lumberjack
Bedrock
WordPress v4.5.*
Timber
v1.1.*
Yoast
v5.2.3
ACF
v5.6.1.2
Lumberjack Core v3.0.*
https://github.com/Rareloop/lumberjack
What does it
all look like?
Feature tour…
It’s designed so you can use as much or as little as you need
Controllers
🍰
$context = Timber::get_context();
// Get custom meta from current page
$context['customTitle'] = get_post_meta(the_ID(), 'customTitle');
$context['customDescription'] = get_post_meta(the_ID(), 'customDescription');
// Get list of posts to show
$context['posts'] = Timber::get_posts();
Timber::render('index.twig', $context);
index.php
namespace App;
use RareloopLumberjackHttpResponsesTimberResponse;
use RareloopLumberjackPost;
use TimberTimber;
class IndexController
{
public function handle()
{
$context = Timber::get_context();
// Get custom meta from current page
$context['customTitle'] = get_post_meta(the_ID(), 'customTitle');
$context['customDescription'] = get_post_meta(the_ID(), 'customDescription');
// Get list of posts to show
$context['posts'] = Timber::get_posts();
}
}
Timber::render('index.twig', $context);return new TimberResponse('index.twig', $context);
extends BaseController
index.php
Controller Benefits
• Procedural -> Object Orientated code
• Can use inheritance to extend a base class for common functionality
• Can encapsulate more complex routines in private functions
• Make use of PSR-7 compliant Response objects
Config
🍰
// config/app.php
return [
'debug' => true,
'environment' => getenv('WP_ENV'),
'logs' => [
'enabled' => true,
'location' => '/tmp',
],
];
// functions.php
use RareloopLumberjackFacadesConfig;
Config::get('app.debug'); // => true
Config::get('app.logs.location'); // => '/tmp'
Config: Out of the box
• Post Type Registration
• Image sizes
• Logging
• Menus
• Load paths of Twig files for Timber
• Basic theme support (e.g. enable Featured Image support)
Post Objects
🍰
use TimberPost;
$post = new Post(1);
$collection = Timber::get_posts($wpQueryArray);
Timber Post Object
use RareloopLumberjackPost;
$post = new Post(1);
$collection = Post::query($wpQueryArray);
Lumberjack Post Object
class Product extends Post
{
public function getPhotos() : array
{
// Do database query to get the assigned photos
}
}
Encapsulate Business Logic
$product = new Product(123);
$photos = $product->getPhotos();
class Product extends Post
{
}
Register Custom Post Types
// config/posttypes.php
return [
'register' => [
AppPostTypesProduct::class,
],
];
protected static function getPostTypeConfig()
{
return [
'labels' => [
'name' => __('Products'),
'singular_name' => __('Product'),
],
'public' => true,
'has_archive' => false,
];
}
public static function getPostType()
{
return 'product';
}
Query Builder
Advanced Queries
$productType = new ProductType;
$products = Product::whereStatus('publish')
->whereMeta('type', '"' . $productType->id . '"', 'LIKE')
->limit(10)
->orderBy('title', 'desc')
->get();
Using the Query Builder directly
$products = QueryBuilder::wherePostType([
Product::getPostType(),
GiftSet::getPostType(),
])
->whereStatus('publish')
->limit(10)
->orderBy(‘title', 'desc')
->get();
Extending the Query Builder
$products = Product::whereStatus('publish')
->whereWrittenSince('10 days ago')
->whereSearchTerm('Lorem Ipsum')
->orderBy('date', 'desc')
->paginate();
Routing
🍰
use RareloopLumberjackFacadesRouter;
use ZendDiactorosResponseHtmlResponse;
Router::get('hello/world', function () {
return HtmlResponse('<h1>Hello World!</h1>');
});
Simple Routes
Router::get('hello/{name}', function ($name) {
return HtmlResponse('<h1>Hello ' . $name . '!</h1>');
});
echo Router::url('hello.world', ['name' => 'adam']);
->name('hello.world');
Params & Named Routes
Router::get('hello/world', 'AppControllersHelloController@show');
Controller Definitions
AJAX endpoint that returns JSON
class ArticleCommentController
{
}
Router::post('articles/{id}/comments', 'AppHttpControllersArticleCommentController@store');
public function store($articleId, ServerRequest $request) {
}
$content = json_decode($request->getBody(), true);
$user = wp_get_current_user();
wp_new_comment([
'comment_post_ID' => (int) $articleId,
'comment_author' => $user->ID,
'comment_content' => data_get($content, 'comment'),
]);
return new JsonResponse([
'data' => [
'content' => $content['comment'],
'author' => $user->display_name,
]
], 201);
Middleware
Middleware
Application
Request Response
Router::get('hello/world', function ($name) {
return JsonResponse({
text: 'Hello World'
});
});->middleware(new CorsMiddleware);
Middleware
Router benefits
• Extend WordPress site with custom URL endpoints
• Access to all REST based verbs
• Can add Groups, for collecting similar resources together
• PSR-15 compatible Middleware
Responses
🍰
Baked-in Responses
$response = new TimberResponse('templates/home', $context);
// From https://github.com/zendframework/zend-diactoros
$response = new TextResponse('Hello world!');
$response = new HtmlResponse($htmlContent);
$response = new XmlResponse($xml);
$response = new JsonResponse($data);
$response = new EmptyResponse(); // Basic 204 response:
$response = new RedirectResponse('/user/login');
Collections
🍰
Collections provide a fluent,
convenient and expressive wrapper
for working with arrays of data
https://laravel.com/docs/5.6/collections
Using Collections
// Standard approach
$customerEmails = [];
foreach ($customers as $customer) {
$customerEmails[] = $customer->email;
}
return $customerEmails;
// Using collections
return collect($customers)->map(function ($customer) {
return $customer->email;
});
// Using higher order functions
return collect($customers)->map->email;
Available Collection Methods
Other helper functions
// If the given value is not an array, wrap it in one.
$array = array_wrap(1); // [1]
$array = array_wrap([1]) // [1]
// Get an item from an array or object using "dot" notation
$avatar = data_get($user, 'avatar.src', '/img/default.png');
// Dump the passed variables and end the script.
dd($user);
• Dependency Injection Container (using PHP-DI)
• Facades
• Exception Handling
• Validation (currently an external package)
• View Models
• Service Providers
Other shiny things ✨
• Command-line interface
• (Optional) Global helper functions (e.g. config(‘app.providers’) )
• Automated Testing
• Sessions (currently in development)
• Example theme (currently in development)
• ‘Responsable’ exceptions (from Laravel)
• Improve documentation
On the radar 📡
• There’s a lot of power available if you want it
• Helps you write more maintainable code (it’s easier to change)
• Easier to re-use code between projects
• Very extendable
• Use only what you need to
• We’re working on improving the documentation
• If you have any questions feel free to drop us a message (Slack or Twitter)
• Get involved (PRs, issues, documentation etc)
Wrapping up
Questions?

Supercharging WordPress Development in 2018

  • 1.
    Supercharging WordPress Development in2018 by Adam Tomat, Rareloop LUMBERJACK @adamtomat
  • 2.
  • 3.
    • PHP-FIG andPSRs • The “problem” • What’s available in Lumberjack • Controllers • Config • Post Objects • Query Builder • Routing • Responses • Collections • Other shiny things in Lumberjack • On the radar What We’ll Cover…
  • 4.
    PHP-FIG &
 PSRs PHP Framework InteropGroup PHP Standards Recommendations https://www.php-fig.org/psr PSR11 - PSR7 - PSR15 - PSR3
  • 5.
    What problem arewe solving?
  • 6.
    CONTENT SITE BESPOKESYSTEM Project Spectrum
  • 7.
    Lumberjack 1 Aimed tomake development more sane, came up with some conventions. Stopped short of being a full framework. Built on two open source projects Launched end of 2015
  • 8.
  • 9.
    index.php <h1><?php echo get_post_meta(get_the_ID(),'customTitle'); ?></h1> <p><?php echo get_post_meta(get_the_ID(), 'customDescription'); ?></p> <?php if (have_posts()) : while (have_posts()) : the_post(); ?> <div id="post-<?php the_ID(); ?>"> <h1><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h1> <?php the_content(); ?> </div> <?php endwhile; ?>
  • 10.
    <h1>{{ customTitle }}</h1> <p>{{customDescription }}</p> {% for post in posts %} <div id="post-{{ post->id }}"> <h2><a href="{{ post->permalink }}">{{ post->title }}</a></h2> {{ post->content|raw }} </div> {% endfor %} index.twig $context = Timber::get_context(); // Get custom meta from current page $context['customTitle'] = get_post_meta(the_ID(), 'customTitle'); $context['customDescription'] = get_post_meta(the_ID(), 'customDescription'); // Get list of posts to show $context['posts'] = Timber::get_posts(); Timber::render('index.twig', $context); index.php
  • 11.
  • 12.
  • 13.
  • 14.
    Theme The new Lumberjack Bedrock WordPressv4.5.* Timber v1.1.* Yoast v5.2.3 ACF v5.6.1.2 Lumberjack Core v3.0.*
  • 15.
  • 16.
    Feature tour… It’s designedso you can use as much or as little as you need
  • 17.
  • 18.
    $context = Timber::get_context(); //Get custom meta from current page $context['customTitle'] = get_post_meta(the_ID(), 'customTitle'); $context['customDescription'] = get_post_meta(the_ID(), 'customDescription'); // Get list of posts to show $context['posts'] = Timber::get_posts(); Timber::render('index.twig', $context); index.php
  • 19.
    namespace App; use RareloopLumberjackHttpResponsesTimberResponse; useRareloopLumberjackPost; use TimberTimber; class IndexController { public function handle() { $context = Timber::get_context(); // Get custom meta from current page $context['customTitle'] = get_post_meta(the_ID(), 'customTitle'); $context['customDescription'] = get_post_meta(the_ID(), 'customDescription'); // Get list of posts to show $context['posts'] = Timber::get_posts(); } } Timber::render('index.twig', $context);return new TimberResponse('index.twig', $context); extends BaseController index.php
  • 20.
    Controller Benefits • Procedural-> Object Orientated code • Can use inheritance to extend a base class for common functionality • Can encapsulate more complex routines in private functions • Make use of PSR-7 compliant Response objects
  • 21.
  • 22.
    // config/app.php return [ 'debug'=> true, 'environment' => getenv('WP_ENV'), 'logs' => [ 'enabled' => true, 'location' => '/tmp', ], ]; // functions.php use RareloopLumberjackFacadesConfig; Config::get('app.debug'); // => true Config::get('app.logs.location'); // => '/tmp'
  • 23.
    Config: Out ofthe box • Post Type Registration • Image sizes • Logging • Menus • Load paths of Twig files for Timber • Basic theme support (e.g. enable Featured Image support)
  • 24.
  • 25.
    use TimberPost; $post =new Post(1); $collection = Timber::get_posts($wpQueryArray); Timber Post Object
  • 26.
    use RareloopLumberjackPost; $post =new Post(1); $collection = Post::query($wpQueryArray); Lumberjack Post Object
  • 27.
    class Product extendsPost { public function getPhotos() : array { // Do database query to get the assigned photos } } Encapsulate Business Logic $product = new Product(123); $photos = $product->getPhotos();
  • 28.
    class Product extendsPost { } Register Custom Post Types // config/posttypes.php return [ 'register' => [ AppPostTypesProduct::class, ], ]; protected static function getPostTypeConfig() { return [ 'labels' => [ 'name' => __('Products'), 'singular_name' => __('Product'), ], 'public' => true, 'has_archive' => false, ]; } public static function getPostType() { return 'product'; }
  • 29.
  • 30.
    Advanced Queries $productType =new ProductType; $products = Product::whereStatus('publish') ->whereMeta('type', '"' . $productType->id . '"', 'LIKE') ->limit(10) ->orderBy('title', 'desc') ->get();
  • 31.
    Using the QueryBuilder directly $products = QueryBuilder::wherePostType([ Product::getPostType(), GiftSet::getPostType(), ]) ->whereStatus('publish') ->limit(10) ->orderBy(‘title', 'desc') ->get();
  • 32.
    Extending the QueryBuilder $products = Product::whereStatus('publish') ->whereWrittenSince('10 days ago') ->whereSearchTerm('Lorem Ipsum') ->orderBy('date', 'desc') ->paginate();
  • 33.
  • 34.
    use RareloopLumberjackFacadesRouter; use ZendDiactorosResponseHtmlResponse; Router::get('hello/world',function () { return HtmlResponse('<h1>Hello World!</h1>'); }); Simple Routes
  • 35.
    Router::get('hello/{name}', function ($name){ return HtmlResponse('<h1>Hello ' . $name . '!</h1>'); }); echo Router::url('hello.world', ['name' => 'adam']); ->name('hello.world'); Params & Named Routes
  • 36.
  • 37.
    AJAX endpoint thatreturns JSON class ArticleCommentController { } Router::post('articles/{id}/comments', 'AppHttpControllersArticleCommentController@store'); public function store($articleId, ServerRequest $request) { } $content = json_decode($request->getBody(), true); $user = wp_get_current_user(); wp_new_comment([ 'comment_post_ID' => (int) $articleId, 'comment_author' => $user->ID, 'comment_content' => data_get($content, 'comment'), ]); return new JsonResponse([ 'data' => [ 'content' => $content['comment'], 'author' => $user->display_name, ] ], 201);
  • 38.
  • 39.
    Router::get('hello/world', function ($name){ return JsonResponse({ text: 'Hello World' }); });->middleware(new CorsMiddleware); Middleware
  • 40.
    Router benefits • ExtendWordPress site with custom URL endpoints • Access to all REST based verbs • Can add Groups, for collecting similar resources together • PSR-15 compatible Middleware
  • 41.
  • 42.
    Baked-in Responses $response =new TimberResponse('templates/home', $context); // From https://github.com/zendframework/zend-diactoros $response = new TextResponse('Hello world!'); $response = new HtmlResponse($htmlContent); $response = new XmlResponse($xml); $response = new JsonResponse($data); $response = new EmptyResponse(); // Basic 204 response: $response = new RedirectResponse('/user/login');
  • 43.
  • 44.
    Collections provide afluent, convenient and expressive wrapper for working with arrays of data https://laravel.com/docs/5.6/collections
  • 45.
    Using Collections // Standardapproach $customerEmails = []; foreach ($customers as $customer) { $customerEmails[] = $customer->email; } return $customerEmails; // Using collections return collect($customers)->map(function ($customer) { return $customer->email; }); // Using higher order functions return collect($customers)->map->email;
  • 46.
  • 47.
    Other helper functions //If the given value is not an array, wrap it in one. $array = array_wrap(1); // [1] $array = array_wrap([1]) // [1] // Get an item from an array or object using "dot" notation $avatar = data_get($user, 'avatar.src', '/img/default.png'); // Dump the passed variables and end the script. dd($user);
  • 48.
    • Dependency InjectionContainer (using PHP-DI) • Facades • Exception Handling • Validation (currently an external package) • View Models • Service Providers Other shiny things ✨
  • 49.
    • Command-line interface •(Optional) Global helper functions (e.g. config(‘app.providers’) ) • Automated Testing • Sessions (currently in development) • Example theme (currently in development) • ‘Responsable’ exceptions (from Laravel) • Improve documentation On the radar 📡
  • 50.
    • There’s alot of power available if you want it • Helps you write more maintainable code (it’s easier to change) • Easier to re-use code between projects • Very extendable • Use only what you need to • We’re working on improving the documentation • If you have any questions feel free to drop us a message (Slack or Twitter) • Get involved (PRs, issues, documentation etc) Wrapping up
  • 51.