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}";
}
}
Menus
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();
}
}
Changelog
License
This package is open-sourced software licensed under the MIT license.