Skip to content

Instantly share code, notes, and snippets.

@collegeman
Last active September 5, 2022 06:59
Show Gist options
  • Save collegeman/ccfc7433e3c64e7814cb8484283335f4 to your computer and use it in GitHub Desktop.
Save collegeman/ccfc7433e3c64e7814cb8484283335f4 to your computer and use it in GitHub Desktop.
Boilerplated files for Lumen-based WordPress plugins

How to build a WordPress plugin using Lumen

Do this:

  1. Use WP-CLI to download a clean copy of WordPress: wp download
  2. Run WordPress locally using Laravel Valet
  3. Setup WordPress—use WP-CLI or the Web UI, doesn't matter
  4. Install and activate the WP REST project: wp plugin install rest-api && wp plugin activate rest-api
  5. Install Lumen globally: https://lumen.laravel.com/docs/5.3/installation
  6. Create a new Lumen project in the wp-content/plugins folder: lumen new {$pluginName}
  7. Copy in config/app.php and config/database.php (attached to this Gist)
  8. Remove the contents of app/Http/routes.php—we'll be using the WP REST API for routing
  9. Modify bootstrap/app.php: uncomment both $app->withFacades(); and $app->withEloquent();, and add $app->configure('app');
  10. Optionally, add my custom Handler implementation: copy Handler.php to {$pluginName}/app/Exceptions; this implementation turns Exceptions into JSON responses instead of marking them up with HTML
  11. Copy plugin.php (attached to this Gist) to your plugin's path, e.g., {$pluginName}/plugin.php; this is your plugin's main file, where you'll build out your routes
  12. Make sure to remove the /vendor entry from .gitignore so that if/when you distribute your WordPress plugin, it will ship with all the necessary dependencies

Now you can do this:

  1. Use Laravel's Service Container to add complex functionality to your plugins: Event Broadcasting, Notifications, Caching, Queing, and Mail
  2. Use Laravel's Eloquent ORM and Migrations to rapidly build your plugin's model code
  3. Run Laravel's artisan CLI tool through WP-CLI: wp {$pluginName} {$artisanCmd} ...
  4. Build awesome things
<?php
return [
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'debug' => env('APP_DEBUG', defined('WP_DEBUG') ? constant('WP_DEBUG') : false),
'key' => env('APP_KEY', defined('AUTH_SALT') ? constant('AUTH_SALT') : 'random-string'),
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => env('APP_LOCALE', 'en'),
/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
];
<?php
global $table_prefix;
return [
/*
|--------------------------------------------------------------------------
| PDO Fetch Style
|--------------------------------------------------------------------------
|
| By default, database results will be returned as instances of the PHP
| stdClass object; however, you may desire to retrieve records in an
| array format for simplicity. Here you can tweak the fetch style.
|
*/
'fetch' => PDO::FETCH_CLASS,
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for all database work. Of course
| you may use many connections at once using the Database library.
|
*/
'default' => 'mysql',
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Here are each of the database connections setup for your application.
| Of course, examples of configuring each database platform that is
| supported by Laravel is shown below to make development simple.
|
|
| All database work in Laravel is done through the PHP PDO facilities
| so make sure you have the driver for your particular database of
| choice installed on your machine before you begin development.
|
*/
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => @constant('DB_HOST'),
'port' => env('DB_PORT', 3306),
'database' => @constant('DB_NAME'),
'username' => @constant('DB_USER'),
'password' => @constant('DB_PASSWORD'),
'charset' => @constant('DB_CHARSET'),
'collation' => @constant('DB_COLLATE'),
'prefix' => $table_prefix,
'strict' => false,
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run in the database.
|
*/
'migrations' => 'fpc_migrations',
];
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Validation\ValidationException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Laravel\Lumen\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that should not be reported.
*
* @var array
*/
protected $dontReport = [
AuthorizationException::class,
HttpException::class,
ModelNotFoundException::class,
ValidationException::class,
];
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* @param \Exception $e
* @return void
*/
public function report(Exception $e)
{
parent::report($e);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $e
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
$response = [
'type' => get_class($e),
'code' => $e->getCode(),
'message' => $e->getMessage(),
'data' => [
'status' => 500
]
];
if ($e instanceof ModelNotFoundException) {
$response['data']['status'] = 404;
}
if ($e instanceof HttpException) {
$response['data']['status'] = $e->getStatusCode();
}
if (config('app.debug')) {
$response['line'] = $e->getLine();
$response['file'] = $e->getFile();
$response['trace'] = $e->getTraceAsString();
}
return response()->json($response, $response['data']['status']);
}
}
<?php
/*
Plugin Name: Your Plugin's Name
Description:
Author: You
Author URI:
Plugin URI:
*/
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\DB;
use FatPanda\Illuminate\Support\Facades\Hashids;
use FatPanda\Illuminate\Support\Exceptions\ValidationException;
use Symfony\Component\HttpKernel\Exception\HttpException;
call_user_func(function() {
$namespace = 'your-plugins-namespace';
$version = 'v1';
/**
* Create and/or get access to the Lumen application container.
* @param $make Can be null, a string, or a function.
* @return If `$make` is a string, returns Application::make($make); if `$make`
* is a function, that function is invoked with the Application instance as the
* first and only argument, returning the result of that function call; otherwise,
* the Application instance is returned.
*/
$app = function($make) {
static $app;
if (empty($app)) {
$app = require __DIR__.'/bootstrap/app.php';
}
if (is_callable($make)) {
return $make($app);
}
return $make ? $app->make($make) : $app;
};
/**
* Register an activation hook that executions any database migrations.
*/
register_activation_hook(__FILE__, function() use ($app) {
$app(function($app) {
if (!Schema::hasTable('fpc_migrations')) {
Artisan::call('migrate:install');
}
Artisan::call('migrate', ['--force' => '1']);
});
});
/**
* Run Laravel artisan from within this plugin
*/
if (class_exists('WP_CLI')) {
WP_CLI::add_command($namespace, function($args) use ($app) {
$app(function($app) use ($args) {
if (empty($args)) {
WP_CLI::error("Unknown artisan command");
exit;
}
Artisan::call(array_shift($args), array_reduce($args, function($result, $arg) {
@list($name, $value) = explode('=', $arg);
$result[$name] = $value ? $value : 1;
return $result;
}, []));
WP_CLI::log(Artisan::output());
});
});
}
add_action('init', function() use ($app, $namespace, $version) {
add_action('rest_api_init', function() use ($app, $namespace, $version) {
/**
* Create a simple function for each of the standard HTTP methods,
* each with default (and strong) permissions
*/
foreach(['get', 'post', 'put', 'patch', 'delete'] as $method) {
/**
* @param $route The route to configure
* @param $callback The function invoke when the route is requested
* @param $options Other options to pass to register_rest_route
* @see register_rest_route
*/
$$method = function($route, $callback = null, $options = []) use ($method, $namespace, $version) {
$defaults = [
'permission_callback' => function(WP_REST_Request $request) {
return is_user_logged_in();
},
'methods' => strtoupper($method),
'callback' => $callback,
];
register_rest_route("{$namespace}/{$version}", $route, array_merge($defaults, $options));
};
}
/**
* Create variables for matching IDs and other parameters in URLs
*/
$exampleIdString = "(?P<id>\w+)";
/**
* Default pagination arguments
*/
$pagingArgs = [
'limit' => [
'default' => 15,
'validate_callback' => function($param, $request, $key) {
return is_numeric($param);
}
]
];
///////////////////////////////////////////////////////////////////////////////////////////
//
// Your Routes
//
///////////////////////////////////////////////////////////////////////////////////////////
// GET /resources
$get("/resources", function(WP_REST_Request $request) use ($app) {
return $app(function() use ($request) {
// get paginated list of resources
// e.g., Resource::where('user_id', get_current_user_id())->paginate((int) $request['limit']);
});
}, [
'args' => $pagingArgs
]);
// GET /resources/{$id}
$get("/resources/{$exampleIdString}", function(WP_REST_Request $request) use ($app) {
return $app(function() use ($request) {
// assert ownership of the resource
// query it, e.g., $resource = Resource::findOrFail($request['id'])
// return it, e.g., return $resource
});
});
// other actions:
$post("/resource", function() {}); // CREATE
$put("/resource/{$exampleIdString}", function() {}); // UPDATE
$delete("resource/{$exampleIdString}", function() {}); // DELETE
});
});
});
@ashbeats
Copy link

ashbeats commented Dec 12, 2018

@explainer You are looking for Wordpress' rest api ☃ [ https://developer.wordpress.org/rest-api/ ] ☃

@sdr0x07b6
Copy link

I found a great article.
Thanks to you, it became easier to interact with WordPress. Thank you!

But there are some problems.
I added a model file and executed composer dump-autoload,
but the Eloquent model cannot be used.
For example, Item::get();

"Call to a member function connection() on null",
illuminate/database/Eloquent/Model.php(1253)

If you look into it, you need to uncomment the following in bootstrap/app.php.

$app->withFacades();

$app->withEloquent();

I did this but the model is not available.
The query builder can be used, so the database settings are correct.
DB::table('items')->get(); // OK
How can I use it?

There was another problem.
paginate() doesn't capture ?page=N. The first page is responded no matter how many pages are specified.
This was solved by explicitly specifying the page number, but I want to know the correct solution.

->paginate($params['limit'], ['*'], 'page', $params['page']) // OK...
  • WordPress 5.2.3
  • Lumen 6.1.0

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment