Skip to main content
Docs

iamfredric/platon

Introduction

Platon is yet another framework that goes on top of WordPress, heavily inspired by Laravel.

The goal of this framework is to provide a simple and easy to use API to build WordPress themes.

Installation

Create a new project with composer. Install in the WordPress themes directory.

composer create-project iamfredric/platon-theme your-path --stability=dev
  • Replace all occurances of [THEME_TITLE] with the title of your theme. (style.css + config/app.php)
  • Replace all occurances of [THEME_SLUG] with the slug of your theme. (style.css + config/app.php)

Routing

Normally, WordPress decides what template to render by looking at your file names.

In platon, this works. However, it's not the idea. Instead, there are a routes/web.php-file where we define routes.

The routes correspond with the WordPress template hierarchy.

<?php
 
use App\Http\Controllers\PageController;
use App\Http\Controllers\ProjectController;
use App\Http\Controllers\PostController;
use Platon\Facades\Route;
 
Route::register('front-page', [PageController::class, 'home']); // front-page.php
Route::register('page', [PageController::class, 'show']); // page.php
 
Route::register('index', [PostController::class, 'index']); // index.php
Route::register('single', [PostController::class, 'show']); // single.php
 
Route::register('single-project', [ProjectController::class, 'index']); // single-project.php

Controllers

Controllers are just a place to process the data before it hits the view-layer.

Here are a simple example of a PostController.

<?php
 
namespace App\Http\Controllers;
 
use App\Models\Post;
 
class PostController
{
    public function index()
    {
        return view('posts.index', [
            'posts' => Post::paginate()
        ]);
    }
 
    public function show(Post $post)
    {
        return view('posts.show', compact('post'));
    }
}

The arguments gets resolved out of the container, where the typehinted Post in the show method gets resolved as Post::current().

Models

The models are a fancy wrapper around the Wp_Post object, injected by some handy tricks.

The idea is to have a model-per-post type

<?php
 
namespace App\Models;
 
use Platon\Database\Model;
 
class Post extends Model
{}

This example will render the records from the posts table with a post_type set to post. This is based on the name of the model class, but you can tweak this by setting the type to something else.

<?php
 
namespace App\Models;
 
use Platon\Database\Model;
 
class Post extends Model
{
    protected $type = 'open-source'; 
}

Accessors, Casts, Thumbnails and Advanced Custom Fields

Accessors work like the old school Laravel ones, if you want to modify your title you prepend it with get and append it with Attribute, getTitleAttribute.

If working with Advanced Custom Fields, this can be handled by the model.

Casts can be specified on a field or an acf-field.

When having a post thumbnail, you can automatically cast it into an WpImage object

<?php
 
namespace App\Models;
 
use Platon\Database\AdvancedCustomFields;
use Platon\Database\FormatedContent;
use Platon\Database\Model;
use Platon\Database\Thumbnail;
 
class Post extends Model
{
    // By default, the $post->content does not wrap the content in apply_filters('the_content'),
    // To make this happen, just add this trait
    use FormatedContent;
 
    // When you have a thumbnail on your post, you can cast it
    // By using this trait, $post->thumbnail will return a WpImage instance
    use Thumbnail;
 
    // When using Advanced custom fields,
    // this trait makes it possble to $post->fields->get('name')
    use AdvancedCustomFields;
 
    // Cast a field or variable to a custom object
    protected $casts = [
        // This will map through the videos array and cast each one into a Video
        // It will look in the original attributes and the ones from Advanced custom fields
        'videos.*' => Video::class,
        // This will cast the content into a Stringable
        'content' => Stringable::class
    ];
 
    // The naming convention is
    // Str::of($attribute)->camel()->ucFirst()->prepend('get')->append('Attribute');
    public function getTitleAttribute($title)
    {
        return "BREAKING: {$title}";
    }
}

In the routes folder you will find the place to register your menus.

<?php
 
use \Platon\Facades\Menu;
 
Menu::register('main-menu', 'The main menu');
Menu::register('footer-menu', 'The menu in the footer');

Images

In the routes folder you will find the place to register your custom image sizes.

<?php
 
use Platon\Facades\Image;
 
// Adds support for post and page image
Image::support('post', 'page');
 
// Registers an images size of 200x200 cropped with the name "200x200"
Image::register('200x200');
 
// Registers an images size of 1000x500 cropped with the name "another-one"
Image::register('another-one')->width(1000)->height(500)->crop();
 
// Registers an image size of 200x100 scaled with the name "200x100"
Image::register('200x100')->scale();

Post types

In the routes folder you will find the place to register your custom post types.

<?php
 
use Platon\Facades\Posttype;
 
// A public post type
Posttype::register('open-source')
    ->singular('Open source')
    ->plural('Open sources')
    ->supports('title', 'editor', 'thumbnail', 'excerpt')
    ->useGutenberg();
 
// A private post type with a custom taxonomy
Posttype::register('repository')
    ->singular('Repository')
    ->plural('Repositories')
    ->taxonomy(
        Posttype::taxonomy('repository-category')
            ->singular('Category')
            ->plural('Categories')
            ->multilevel()
            ->isPrivate()
    )
    ->supports('title', 'editor', 'thumbnail')
    ->isPrivate();

Hooks and actions

In the routes folder you will find the place to register your custom hooks and actions.

<?php
 
use \Platon\Facades\Hook;
 
Hook::filter('use_block_editor_for_post', [
    \App\Hooks\RemoveEditor::class, 'filter'
]);
 
Hook::action('admin_head', [
    \App\Hooks\RemoveEditor::class, 'action'
]);
 
Hook::action('after_setup_theme', function () {
    add_theme_support( 'editor-styles' );
    add_editor_style(mix('css/app.css'));
});

Components

Components are based on Advanced Custom Fields flexible field.

Let me show you an example.

  • We define a flexible content field with the name of "components".
  • We add a layout named "section" with the fields "gallery":type gallery, and "title": type input[string] and "posts": type posts,multiple

In our Page model

<?php
 
namespace App\Models;
 
use Platon\Database\AdvancedCustomFields;
use Platon\Database\Components;
use Platon\Database\Model;
 
class Page extends Model
{
    use AdvancedCustomFields, 
        Components; 
}

We create a view blocks/section.blade.php

<!-- In the parent (pages.show) -->
@foreach ($page->components as $component)
    {!! $component->render() !!}
@endforeach
 
<!-- In the component -->
<section>
    <h2>
        {{ $title }}
    </h2>
 
    @foreach ($posts as $post)
        <x-posts.post :post="$post" />
    @endforeach
</section>

If the data needs to be marinated, you could create a Class for this in app/Components/SectionComponent.php

<?php
 
namespace App\Components;
 
use Platon\Components\Component;
use App\Models\Post;
 
class SectionComponent extends Component
{
    // This will cast every item in the posts array to a Post
    protected $casts = [
        'posts.*' => Post::class
    ];
 
    // This method will only get triggered if the posts array is empty
    public function whenPostsIsNull()
    {
        return Post::query()
            ->latest()
            ->limit();
    }
}

Issues

Issues are handled on github

For security issues, please contact me in private.

License

This package is open-sourced software licensed under the MIT license.