Meta Boxes
Introduction
Meta boxes in Botble CMS provide a way to add custom fields and data to models. This concept is inspired by WordPress meta boxes but has been enhanced with Laravel's powerful features. Meta boxes allow you to extend existing models with additional fields without modifying the database schema.
Meta boxes are stored in the meta_boxes
table with the following structure:
meta_key
: The key to identify the meta datameta_value
: The value of the meta data (stored as JSON)reference_id
: The ID of the modelreference_type
: The class name of the model
Architecture
The meta box system consists of several components:
- MetaBox Class: The main class that handles adding, retrieving, and saving meta boxes
- MetaBox Model: The Eloquent model that represents the meta_boxes table
- HasMetadata Trait: A trait that can be added to models to provide meta data functionality
- Form Integration: Integration with the form builder for easy creation of meta box fields
Adding Meta Boxes
Using the Action Hook
The most common way to add a meta box is by using the BASE_ACTION_META_BOXES
action hook:
use Botble\Base\Facades\MetaBox;
add_action(BASE_ACTION_META_BOXES, function (string $context, $object) {
if (is_plugin_active('blog') && $object instanceof \Botble\Blog\Models\Post && $context == 'advanced') {
MetaBox::addMetaBox(
'additional_post_fields',
__('Additional Information'),
'post_additional_fields',
get_class($object),
$context,
'default'
);
}
}, 120, 2);
MetaBox::addMetaBox Parameters
MetaBox::addMetaBox(
string $id,
string $title,
string|array|callable|Closure $callback,
?string $reference = null,
string $context = 'advanced',
string $priority = 'default',
?array $callbackArgs = null
);
- $id: (Required) Unique identifier for the meta box
- $title: (Required) Title displayed in the meta box header
- $callback: (Required) Function that renders the meta box content
- $reference: (Optional) The model class name where the meta box should appear
- $context: (Optional) The position of the meta box ('advanced', 'side', 'normal')
- $priority: (Optional) The priority within the context ('high', 'default', 'low')
- $callbackArgs: (Optional) Additional arguments passed to the callback function
Creating the Callback Function
The callback function is responsible for rendering the meta box content:
function post_additional_fields() {
$videoLink = null;
$args = func_get_args();
if (!empty($args[0])) {
$videoLink = MetaBox::getMetaData($args[0], 'video_link', true);
}
return Theme::partial('post-fields', compact('videoLink'));
}
The callback function receives the following arguments:
- The model instance
- The reference (model class name)
- The meta box configuration
Creating the View
Create a view file to render the meta box content:
{{-- your-theme/partials/post-fields.blade.php --}}
<div class="form-group mb-3">
<label for="video-link" class="form-label">{{ __('Video URL') }}</label>
<input type="text" name="video_link" value="{{ $videoLink }}" class="form-control" id="video-link">
</div>
TIP
You can place the view in your theme's partials directory or in your plugin's views directory. If using a plugin view, replace Theme::partial('post-fields')
with view('plugins.your-plugin.partials.post-fields')
.
Saving Meta Box Data
To save meta box data, use the BASE_ACTION_AFTER_CREATE_CONTENT
and BASE_ACTION_AFTER_UPDATE_CONTENT
action hooks:
use Botble\Base\Facades\MetaBox;
add_action([BASE_ACTION_AFTER_CREATE_CONTENT, BASE_ACTION_AFTER_UPDATE_CONTENT], function ($type, $request, $object) {
if ($object instanceof \Botble\Blog\Models\Post) {
MetaBox::saveMetaBoxData($object, 'video_link', $request->input('video_link'));
}
}, 230, 3);
MetaBox::saveMetaBoxData Parameters
MetaBox::saveMetaBoxData(Model $object, string $key, $value, $options = null);
- $object: (Required) The model instance to save meta data for
- $key: (Required) The meta key
- $value: (Required) The meta value
- $options: (Optional) Additional options for the meta data
Retrieving Meta Box Data
Getting a Single Value
$videoLink = MetaBox::getMetaData($post, 'video_link', true);
Getting Multiple Values
$metaValues = MetaBox::getMetaData($post, 'gallery_images');
MetaBox::getMetaData Parameters
MetaBox::getMetaData(
Model $object,
string $key,
bool $single = false,
array $select = ['meta_value']
);
- $object: (Required) The model instance
- $key: (Required) The meta key
- $single: (Optional) Whether to return a single value or an array
- $select: (Optional) The columns to select from the meta_boxes table
Deleting Meta Box Data
MetaBox::deleteMetaData($post, 'video_link');
Advanced Usage
Using with Form Builder
You can integrate meta boxes with the Form Builder by using the BASE_FILTER_BEFORE_RENDER_FORM
filter:
use Botble\Base\Facades\MetaBox;
use Botble\Base\Forms\Fields\TextField;
use Botble\Base\Forms\FieldOptions\TextFieldOption;
add_filter(BASE_FILTER_BEFORE_RENDER_FORM, function ($form, $data) {
if ($data instanceof \Botble\Blog\Models\Post) {
$metaValue = MetaBox::getMetaData($data, 'custom_field', true);
$form
->add(
'custom_field',
TextField::class,
TextFieldOption::make()
->label(__('Custom Field'))
->value($metaValue)
);
}
return $form;
}, 120, 2);
add_action([BASE_ACTION_AFTER_CREATE_CONTENT, BASE_ACTION_AFTER_UPDATE_CONTENT], function ($screen, $request, $data) {
if ($data instanceof \Botble\Blog\Models\Post) {
MetaBox::saveMetaBoxData($data, 'custom_field', $request->input('custom_field'));
}
}, 120, 3);
Using with HasMetadata Trait
Models can implement the HasMetadata
trait to directly access meta data:
namespace App\Models;
use Botble\Base\Models\BaseModel;
use Botble\Base\Models\Concerns\HasMetadata;
class CustomModel extends BaseModel
{
use HasMetadata;
// ... other model code
}
Then you can access meta data directly from the model:
$model = CustomModel::find(1);
$value = $model->getMetaData('custom_field', true);
Creating Complex Meta Boxes
For more complex meta boxes, you can use closures as callbacks:
MetaBox::addMetaBox(
'seo_wrap',
__('SEO Configuration'),
function () {
$args = func_get_args();
$model = $args[0];
$meta = [
'seo_title' => null,
'seo_description' => null,
'seo_image' => null,
];
if ($model->id) {
$metadata = MetaBox::getMetaData($model, 'seo_meta', true);
if ($metadata && is_array($metadata)) {
$meta = array_merge($meta, $metadata);
}
}
return view('plugins.seo.meta-box', compact('meta', 'model'))->render();
},
get_class($model)
);
Real-World Examples
Gallery Meta Box
The Gallery plugin uses meta boxes to store images for various models:
MetaBox::addMetaBox(
'gallery_wrap',
__('Gallery'),
[$this, 'galleryMetaField'],
$object::class,
'advanced'
);
// Callback method
public function galleryMetaField($object)
{
$value = gallery_meta_data($object);
return view('plugins.gallery.gallery-box', compact('value', 'object'))->render();
}
// Saving the data
add_action(BASE_ACTION_AFTER_CREATE_CONTENT, function ($type, $request, $object) {
if (in_array(get_class($object), Gallery::getSupportedModules()) && $request->has('gallery')) {
MetaBox::saveMetaBoxData($object, 'gallery', $request->input('gallery'));
}
}, 230, 3);
SEO Meta Box
The SEO Helper package uses meta boxes to store SEO configuration:
MetaBox::addMetaBox(
'seo_wrap',
__('SEO Configuration'),
[$this, 'seoMetaBox'],
$object::class,
'advanced',
'low'
);
// Callback method
public function seoMetaBox()
{
$args = func_get_args();
$object = $args[0];
$meta = [
'seo_title' => null,
'seo_description' => null,
'seo_image' => null,
];
if ($object->id) {
$metadata = MetaBox::getMetaData($object, 'seo_meta', true);
if ($metadata && is_array($metadata)) {
$meta = array_merge($meta, $metadata);
}
}
return view('packages.seo-helper.meta-box', compact('meta', 'object'))->render();
}
// Saving the data
public function saveMetaData($screen, $request, $object)
{
if (in_array(get_class($object), config('packages.seo-helper.general.supported', []))) {
MetaBox::saveMetaBoxData($object, 'seo_meta', $request->input('seo_meta', []));
}
}
Best Practices
Use Unique Keys: Always use unique meta keys to avoid conflicts with other plugins or themes.
Validate Input: Always validate user input before saving meta data.
Use Namespaced Keys: Consider prefixing your meta keys with your plugin or theme name to avoid conflicts.
Check for Existence: Always check if the model exists before saving meta data.
Use Appropriate Hooks: Use the correct action hooks for adding and saving meta boxes.
Optimize Queries: When retrieving multiple meta values, consider using a single query instead of multiple queries.
Clean Up: Delete meta data when the associated model is deleted to prevent orphaned records.
Complete Example
Here's a complete example of adding a meta box to the Post model:
// In your-theme/functions/functions.php or plugin service provider
use Botble\Base\Facades\MetaBox;
use Botble\Blog\Models\Post;
use Botble\Theme\Facades\Theme;
// Add the meta box
add_action(BASE_ACTION_META_BOXES, function (string $context, $object) {
if ($object instanceof Post && $context == 'advanced') {
MetaBox::addMetaBox(
'additional_post_fields',
__('Additional Information'),
'post_additional_fields',
get_class($object),
$context,
'default'
);
}
}, 24, 2);
// Callback function to render the meta box
function post_additional_fields() {
$videoLink = null;
$downloadLink = null;
$args = func_get_args();
if (!empty($args[0])) {
$videoLink = MetaBox::getMetaData($args[0], 'video_link', true);
$downloadLink = MetaBox::getMetaData($args[0], 'download_link', true);
}
return Theme::partial('post-fields', compact('videoLink', 'downloadLink'));
}
// Save the meta box data
add_action([BASE_ACTION_AFTER_CREATE_CONTENT, BASE_ACTION_AFTER_UPDATE_CONTENT], function ($type, $request, $object) {
if ($object instanceof Post) {
MetaBox::saveMetaBoxData($object, 'video_link', $request->input('video_link'));
MetaBox::saveMetaBoxData($object, 'download_link', $request->input('download_link'));
}
}, 230, 3);
And the view file:
{{-- your-theme/partials/post-fields.blade.php --}}
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label for="video-link" class="form-label">{{ __('Video URL') }}</label>
<input type="text" name="video_link" value="{{ $videoLink }}" class="form-control" id="video-link">
<small class="form-text text-muted">{{ __('YouTube or Vimeo video URL') }}</small>
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label for="download-link" class="form-label">{{ __('Download Link') }}</label>
<input type="text" name="download_link" value="{{ $downloadLink }}" class="form-control" id="download-link">
<small class="form-text text-muted">{{ __('Link to downloadable file') }}</small>
</div>
</div>
</div>