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
});
});
});
@explainer
Copy link

I am very new to WordPress, and have never written a line of PHP. Lots of Ruby, C, Javascript (CoffeeScript). I have a Wordpress app running, displaying a portion of an aircraft museums collection online. So far, so good. I am looking for ways to connect the WP app to the rest of the world (making paypal payments, sending email messages, managing user creation and logins, etc.). Can you provide more context for what your code is intended for?

@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