Supercharging 

WordPress Development
by Adam Tomat @adamtomat
Slack
Digital Product Studio
Projects
vs
Products
Projects
Cost of Introduction
- Only adding new code (e.g. controllers, models etc)
Cost of Change
- Making changes to existing code
Cost of Ownership
- Refactoring, writing tests etc
Products
Konstantin Kudryashov - Laracon EU 2015
– Liz Keogh
“You’re not really ‘done’ until the software has been
retired and is no longer used at all”
Make your website

easier to change.
I ❤ PHP
I ❤ WordPress
“Not Waving But Drowning” - Stevie Smith
Starts with drips
Cognitive Behavioural Therapy
• Traffic on the way to work
• Worrying about your body image
• Not getting enough sleep
• Financial difficulties
• Worries about keeping a job
• Being isolated
Life: What can fill up your bathtub?
• Slow getting setup on project
• Applying the same change/code across multiple files
• Unstructured and undocumented setup
• Introducing bugs or regressions
• Painful deployments
• Different conventions/style within the code (inconsistent)
• Difficult to change
• Unable to change the current situation
WordPress: What fills your bathtub?
What does this code look like?
$the_query = new WP_Query([
'posts_per_page' => 5,
'tag' => 'club'
]);
if ($the_query->have_posts()) : ?>
<h4>Recent Articles</h4>
<ul>
<?php while ( $the_query->have_posts() ) : $the_query->the_post(); ?>
<li class="article">
<h3><?php the_title(); ?></h3>
<?php the_excerpt(10); ?>
<a href="<?php the_permalink(); ?>">Read more</a>
</li>
<?php endwhile; ?>
</ul>
<?php endif; ?>
t
Mix of concerns:
Presentation logic &
Database queries
"
This file has multiple
reasons to change
index.php
$the_query = new WP_Query([
'posts_per_page' => 5,
'tag' => 'club'
]);
if ($the_query->have_posts()) : ?>
<h4>Recent Articles</h4>
<ul>
<?php while ( $the_query->have_posts() ) : $the_query->the_post(); ?>
<li class="article">
<h3><?php the_title(); ?></h3>
<?php the_excerpt(10); ?>
<a href="<?php the_permalink(); ?>">Read more</a>
</li>
<?php endwhile; ?>
</ul>
<?php endif; ?>
t
Procedural
"
Procedural code is
often not DRY or
reusable.



It’s difficult to change.
Hard to read.
index.php
odel
C
M
V iew
ontroller
https://www.rareloop.com/posts/comparing-modern-mvc-wordpress-frameworks/
WordPress MVC Frameworks
CONTENT SITE BESPOKE SYSTEM
Project Spectrum
Timber!
<?php
$context = Timber::get_context();
$context['posts'] = Timber::get_posts([
'posts_per_page' => 5,
'tag' => 'club'
]);
Timber::render('index.twig', $context);
{% if posts is not empty %}
<h4>Recent Articles</h4>
<ul>
{% for post in posts %}
<li class="article">
<h3>{{ $post->title }}</h3>
{{ $post->preview }}
<a href="{{ $post->link }}">Read the full story</a>
</li>
{% endfor %}
</ul>
{% endif %}
index.twig
index.php
What are the problems with Timber
controllers?
<?php
$context = Timber::get_context();
$context['posts'] = Timber::get_posts([
'posts_per_page' => 5,
'tag' => 'club'
]);
Timber::render('index.twig', $context);
index.php
t
Procedural
"
Procedural code is
often not DRY or
reusable.



It’s difficult to change.
Hard to read.
index.php
class IndexController
{
}
public function handle()
{
$context = Timber::get_context();
$context['posts'] = Timber::get_posts([
'posts_per_page' => 5,
'tag' => 'club'
]);
}
extends BaseController
return view('index.twig', $context);
index.php
class IndexController extends BaseController
{
public function handle()
{
$context = Timber::get_context();
$context['posts'] = $this->getPosts();
return view('index.twig', $context);
}
private function getPosts()
{
return Timber::get_posts([
'posts_per_page' => 5,
'tag' => 'club'
]);
}
}
Benefits
• Write Object Orientated code rather than Procedural code
• Separation of concerns
• Easier to write DRY code
• Can use inheritance to extend a base class for common functionality
• Can encapsulate more complex routines in private functions
Setting up a project

&

Deploys
Traditional WordPress
Theme
WordPressTimber
Yoast
ACF
// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'wordpress');
/** MySQL database username */
define('DB_USER', 'root');
/** MySQL database password */
define('DB_PASSWORD', ‘letmein');
/** MySQL hostname */
define('DB_HOST', 'localhost');
/** Database Charset to use in creating database tables. */
define('DB_CHARSET', 'utf8');
/** The Database Collate type. Don't change this if in doubt. */
define('DB_COLLATE', '');
wp-config.php
Bedrock 🙌
Theme
Bedrock
Bedrock
WordPress v5.2.*
Timber
v1.9.*
Wordpress
SEO
v10.0.*
ACF
v5.8.2
How does Lumberjack fit
into this?
Lumberjack Starter Theme
Lumberjack + Bedrock = ❤
Bedrock
Lumberjack Core v4.3.*
WordPress v5.2.*
Timber
v1.9.*
Wordpress
SEO
v10.0.*
ACF
v5.8.2
Take Lumberjack for a spin
Supercharged Config
Some PHP file
$apiKey = config('app.google_maps_api_key');
config/app.php
return [
'google_maps_api_key' => getenv('GOOGLE_MAPS_API_KEY', 'dummy-api-key'),
];
return [
'logs' => [
'enabled' => true,
'location' => '/tmp',
],
];
config/app.php
Some PHP file
$location = config(‘app.logs.location'); // => '/tmp'
Benefits
• Structured file system
• Dependencies managed (install with 1 command)
• Supercharged config
• Easier & safer deploys
Querying Data
$args = [
'post_type' => 'product',
'posts_per_page' => 10,
'orderby' => 'title',
'order' => 'ASC',
'meta_query' => [
'relation' => 'AND',
[
'key' => 'available_from_date',
'value' => [$from_date, $to_date],
'compare' => 'BETWEEN',
'type' => 'DATE',
],
[
'key' => 'available_to_date',
'value' => [$from_date, $to_date],
'compare' => 'BETWEEN',
'type' => 'DATE',
],
]
];
$query = new WP_Query($args);
$posts = $query->get_posts();
use TimberPost;
$post = new Post(1);
$collection = Timber::get_posts($wpQueryArray);
Timber Post Object
use RareloopLumberjackPost;
use AppPostTypesProduct;
$post = new Post(1);
$collection = Post::query($wpQueryArray);
$product = new Product(1);
$collection = Product::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';
}
Supercharged Queries
Advanced Queries
$productType = new ProductType;
$products = Product::builder()
->whereMeta('type', '"' . $productType->id . '"', 'LIKE')
->limit(10)
->orderBy('title', 'asc')
->get();
Using the Query Builder directly
$products = QueryBuilder::wherePostType([
Product::getPostType(),
GiftSet::getPostType(),
])
->limit(10)
->orderBy(‘title', 'asc')
->get();
Supercharged Custom Requests
Router::get('hello/world', function () {
return HtmlResponse('<h1>Hello World!</h1>');
});
Simple Routes
use RareloopLumberjackFacadesRouter;
use ZendDiactorosResponseHtmlResponse;
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', 'HelloController@show');
Controller Definitions
AJAX endpoint that returns JSON
class ArticleCommentController
{
}
Router::post('articles/{id}/comments', 'ArticleCommentController@store');
public function store(int $id)
{
}
$comment = request()->input('comment');
wp_new_comment([
'comment_post_ID' => $id,
'comment_author' => get_current_user_id(),
'comment_content' => $comment,
]);
return new JsonResponse([
'data' => [
'content' => $comment,
]
], 201);
• Extend WordPress site with custom URL endpoints (e.g. for ajax, forms)
• Access to all REST based verbs
• Can add Groups, for collecting similar resources together
• Named routes
• You have a convention & documentation
Router benefits
• PSR11 Dependency Injection Container
(using PHP-DI)
• Facades
• Exception Handling (PSR3)
• Validation (currently an external
package)
• View Models
More power at your fingertips ✨
• Service Providers
• Hatchet (CLI)
• Global helper functions (e.g.
config(‘app.environment’); )
• ‘Responsable’ objects (from Laravel) - in,
but currently undocumented
- Jared Novack - Timber creator
“Lumberjack is the deluxe version of what
Modern WordPress should look like today.



The team has done a great job of making it easy to build
complicated custom applications while taking advantage
of the best parts of WordPress.”
• Evaluate the product mindset for you (and your team?)
• WordPress is awesome, we can make it even more awesome!
• Challenge and push your stack forward. Don’t accept dripping taps
• Write code which is easier to change, where applicable
• Give MVC WordPress a go
• Use only what you need to in Lumberjack
In summary…
docs.lumberjack.rareloop.com
Documentation
rareloop.com/careers
Join Our Team
lumberjack.rareloop.com
Website
Get Involved
⭐
[Bristol WordPress] Supercharging WordPress Development

[Bristol WordPress] Supercharging WordPress Development

  • 1.
    Supercharging 
 WordPress Development byAdam Tomat @adamtomat Slack
  • 2.
  • 3.
  • 4.
    Projects Cost of Introduction -Only adding new code (e.g. controllers, models etc)
  • 5.
    Cost of Change -Making changes to existing code Cost of Ownership - Refactoring, writing tests etc Products
  • 6.
    Konstantin Kudryashov -Laracon EU 2015
  • 7.
    – Liz Keogh “You’renot really ‘done’ until the software has been retired and is no longer used at all”
  • 8.
  • 9.
    I ❤ PHP I❤ WordPress
  • 13.
    “Not Waving ButDrowning” - Stevie Smith
  • 14.
  • 15.
  • 16.
    • Traffic onthe way to work • Worrying about your body image • Not getting enough sleep • Financial difficulties • Worries about keeping a job • Being isolated Life: What can fill up your bathtub?
  • 17.
    • Slow gettingsetup on project • Applying the same change/code across multiple files • Unstructured and undocumented setup • Introducing bugs or regressions • Painful deployments • Different conventions/style within the code (inconsistent) • Difficult to change • Unable to change the current situation WordPress: What fills your bathtub?
  • 18.
    What does thiscode look like?
  • 19.
    $the_query = newWP_Query([ 'posts_per_page' => 5, 'tag' => 'club' ]); if ($the_query->have_posts()) : ?> <h4>Recent Articles</h4> <ul> <?php while ( $the_query->have_posts() ) : $the_query->the_post(); ?> <li class="article"> <h3><?php the_title(); ?></h3> <?php the_excerpt(10); ?> <a href="<?php the_permalink(); ?>">Read more</a> </li> <?php endwhile; ?> </ul> <?php endif; ?> t Mix of concerns: Presentation logic & Database queries " This file has multiple reasons to change index.php
  • 20.
    $the_query = newWP_Query([ 'posts_per_page' => 5, 'tag' => 'club' ]); if ($the_query->have_posts()) : ?> <h4>Recent Articles</h4> <ul> <?php while ( $the_query->have_posts() ) : $the_query->the_post(); ?> <li class="article"> <h3><?php the_title(); ?></h3> <?php the_excerpt(10); ?> <a href="<?php the_permalink(); ?>">Read more</a> </li> <?php endwhile; ?> </ul> <?php endif; ?> t Procedural " Procedural code is often not DRY or reusable.
 
 It’s difficult to change. Hard to read. index.php
  • 21.
  • 22.
  • 24.
    CONTENT SITE BESPOKESYSTEM Project Spectrum
  • 26.
  • 27.
    <?php $context = Timber::get_context(); $context['posts']= Timber::get_posts([ 'posts_per_page' => 5, 'tag' => 'club' ]); Timber::render('index.twig', $context); {% if posts is not empty %} <h4>Recent Articles</h4> <ul> {% for post in posts %} <li class="article"> <h3>{{ $post->title }}</h3> {{ $post->preview }} <a href="{{ $post->link }}">Read the full story</a> </li> {% endfor %} </ul> {% endif %} index.twig index.php
  • 28.
    What are theproblems with Timber controllers?
  • 29.
    <?php $context = Timber::get_context(); $context['posts']= Timber::get_posts([ 'posts_per_page' => 5, 'tag' => 'club' ]); Timber::render('index.twig', $context); index.php t Procedural " Procedural code is often not DRY or reusable.
 
 It’s difficult to change. Hard to read.
  • 31.
    index.php class IndexController { } public functionhandle() { $context = Timber::get_context(); $context['posts'] = Timber::get_posts([ 'posts_per_page' => 5, 'tag' => 'club' ]); } extends BaseController return view('index.twig', $context);
  • 32.
    index.php class IndexController extendsBaseController { public function handle() { $context = Timber::get_context(); $context['posts'] = $this->getPosts(); return view('index.twig', $context); } private function getPosts() { return Timber::get_posts([ 'posts_per_page' => 5, 'tag' => 'club' ]); } }
  • 33.
    Benefits • Write ObjectOrientated code rather than Procedural code • Separation of concerns • Easier to write DRY code • Can use inheritance to extend a base class for common functionality • Can encapsulate more complex routines in private functions
  • 34.
    Setting up aproject
 &
 Deploys
  • 35.
  • 36.
    // ** MySQLsettings - You can get this info from your web host ** // /** The name of the database for WordPress */ define('DB_NAME', 'wordpress'); /** MySQL database username */ define('DB_USER', 'root'); /** MySQL database password */ define('DB_PASSWORD', ‘letmein'); /** MySQL hostname */ define('DB_HOST', 'localhost'); /** Database Charset to use in creating database tables. */ define('DB_CHARSET', 'utf8'); /** The Database Collate type. Don't change this if in doubt. */ define('DB_COLLATE', ''); wp-config.php
  • 37.
  • 38.
  • 39.
    How does Lumberjackfit into this?
  • 40.
    Lumberjack Starter Theme Lumberjack+ Bedrock = ❤ Bedrock Lumberjack Core v4.3.* WordPress v5.2.* Timber v1.9.* Wordpress SEO v10.0.* ACF v5.8.2
  • 42.
  • 43.
  • 44.
    Some PHP file $apiKey= config('app.google_maps_api_key'); config/app.php return [ 'google_maps_api_key' => getenv('GOOGLE_MAPS_API_KEY', 'dummy-api-key'), ];
  • 45.
    return [ 'logs' =>[ 'enabled' => true, 'location' => '/tmp', ], ]; config/app.php Some PHP file $location = config(‘app.logs.location'); // => '/tmp'
  • 46.
    Benefits • Structured filesystem • Dependencies managed (install with 1 command) • Supercharged config • Easier & safer deploys
  • 47.
  • 48.
    $args = [ 'post_type'=> 'product', 'posts_per_page' => 10, 'orderby' => 'title', 'order' => 'ASC', 'meta_query' => [ 'relation' => 'AND', [ 'key' => 'available_from_date', 'value' => [$from_date, $to_date], 'compare' => 'BETWEEN', 'type' => 'DATE', ], [ 'key' => 'available_to_date', 'value' => [$from_date, $to_date], 'compare' => 'BETWEEN', 'type' => 'DATE', ], ] ]; $query = new WP_Query($args); $posts = $query->get_posts();
  • 49.
    use TimberPost; $post =new Post(1); $collection = Timber::get_posts($wpQueryArray); Timber Post Object
  • 50.
    use RareloopLumberjackPost; use AppPostTypesProduct; $post= new Post(1); $collection = Post::query($wpQueryArray); $product = new Product(1); $collection = Product::query($wpQueryArray); Lumberjack Post Object
  • 51.
    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();
  • 52.
    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'; }
  • 53.
  • 54.
    Advanced Queries $productType =new ProductType; $products = Product::builder() ->whereMeta('type', '"' . $productType->id . '"', 'LIKE') ->limit(10) ->orderBy('title', 'asc') ->get();
  • 55.
    Using the QueryBuilder directly $products = QueryBuilder::wherePostType([ Product::getPostType(), GiftSet::getPostType(), ]) ->limit(10) ->orderBy(‘title', 'asc') ->get();
  • 56.
  • 57.
    Router::get('hello/world', function (){ return HtmlResponse('<h1>Hello World!</h1>'); }); Simple Routes use RareloopLumberjackFacadesRouter; use ZendDiactorosResponseHtmlResponse;
  • 58.
    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
  • 59.
  • 60.
    AJAX endpoint thatreturns JSON class ArticleCommentController { } Router::post('articles/{id}/comments', 'ArticleCommentController@store'); public function store(int $id) { } $comment = request()->input('comment'); wp_new_comment([ 'comment_post_ID' => $id, 'comment_author' => get_current_user_id(), 'comment_content' => $comment, ]); return new JsonResponse([ 'data' => [ 'content' => $comment, ] ], 201);
  • 61.
    • Extend WordPresssite with custom URL endpoints (e.g. for ajax, forms) • Access to all REST based verbs • Can add Groups, for collecting similar resources together • Named routes • You have a convention & documentation Router benefits
  • 62.
    • PSR11 DependencyInjection Container (using PHP-DI) • Facades • Exception Handling (PSR3) • Validation (currently an external package) • View Models More power at your fingertips ✨ • Service Providers • Hatchet (CLI) • Global helper functions (e.g. config(‘app.environment’); ) • ‘Responsable’ objects (from Laravel) - in, but currently undocumented
  • 63.
    - Jared Novack- Timber creator “Lumberjack is the deluxe version of what Modern WordPress should look like today.
 
 The team has done a great job of making it easy to build complicated custom applications while taking advantage of the best parts of WordPress.”
  • 64.
    • Evaluate theproduct mindset for you (and your team?) • WordPress is awesome, we can make it even more awesome! • Challenge and push your stack forward. Don’t accept dripping taps • Write code which is easier to change, where applicable • Give MVC WordPress a go • Use only what you need to in Lumberjack In summary…
  • 66.
  • 67.