A Laravel model-based CMS
By Bukwild

View on GitHub
Case study


A lot of Decoy’s “magic” comes by having your admin controllers extend the Bkwld\Decoy\Controllers\Base. The title, description, listing columns, and many other things can be adjusted through the defintion of protected properties in the controller class. To more dramatically affect behavior, you can also override the CRUD action methods of the base controller. These approaches are described below.

Configurable properties

The following protected properties allow you to customize how Decoy works from the parent controller without overriding whole restful methods. They generally affect the behavior of multiple methods. They are all named with all-caps to indicate their significance and to differentiate them from other properties you might set in your admin controller.

The following properties are only relevant if a controller is a parent or child of another, as in hasMany(), belongsToMany(), etc. You can typically use Decoy’s default values for these (which are deduced from the nav Config property).

Search property

The search property takes an array like the following example:


    // 'title' column assumed to be a text type

    // Label auto generated from field name
    'description' => 'text',

    // Most explicit way to define a text field
    'body' => [
        'type' => 'text',
        'label' => 'Body',

    // Creates a pulldown menu
    'type' => [
        'type' => 'select',
        'options' => [
            'photo' => 'Photo',
            'video' => 'Video',

    // Creates a pulldown using static array on Post model
    'category' => [
        'type' => 'select',
        'options' => 'Post::$categories'

    // Numeric input field
    'like_count' => [
        'type' => 'number',
        'label' => 'Like total',

        // Call the static method `likeCountSearch`() on the `Admin\SomeController`
        // class to override the query for the like_count field
        'query' => 'Admin\SomeController::likeCountSearch'

    // Date input field
    'created_at' => 'date',

    // Visibility select menu
    'public' => [
        'label' => 'visibility',
        'type' => 'select',
        'options' => [ 'private', 'public' ],

Several of these properties have accessor functions that can be overrode in your subclass. This has the advantage of allowing you to generate the configuration programmatically or to use closures in the configuration. For instance:

<?php namespace App\Http\Controllers\Admin;
use Bkwld\Decoy\Controllers\Base;
class Articles extends Base {

    public function search() {
        return [

            // Load configuration data from Laravel config()
            'affiliation' => [
                'type' => 'select',
                'options' => config('settings.affiliation'),

            // Support a database "SET" type column in searches
            'type' => [
                'type' => 'select',
                'options' => 'Article::$types',

                // Any search type supports the `query` parameter for change how the
                // field input is applied to the search query
                'query' => function($query, $condition, $input) {
                    $type = DB::connection()->getPdo()->quote($type);
                    $query->whereRaw('FIND_IN_SET('.$type.', articles.type)');

            // Make a toggle for soft deleted columns
            'status' => [
                'type' => 'select',
                'options' => [
                    'deleted' => 'deleted',
                'query' => function($query, $condition, $input) {
                    if ($input == 'deleted') {
                        if ($condition == '=') {
                        } else {

    // Other accessor functions
    public function description() { return ''; }
    public function columns() { return []; }

CRUD actions

Override the following methods to affect behavior for any of the core CRUD methods: