Starting from Laravel 4 all framework components are separate packages that can be used in any PHP project without using the laravel/framework
package.
In this post, I'll demonstrate the usage of illuminate/container
, illuminate/queue
, illuminate/events
, illuminate/redis
and illuminate/database
in a project without the Laravel framework.
Out of all components presented in this post, illuminate/container
is the easiest one to install and use.
<?php
use Illuminate\Container\Container;
// Get the global container instance
// https://laravel.com/api/8.x/Illuminate/Container/Container.html#method_getInstance
$container = Container::getInstance();
// Now, you can use the Container instance just like in Laravel!
return $container->make(Foo::class);
But we can go further and create the app() helper that we can use to quickly resolve dependencies.
Let's start by creating the file called helpers.php
:
<?php
use Illuminate\Container\Container;
// https://github.com/laravel/framework/blob/8.x/src/Illuminate/Foundation/helpers.php#L105-L121
if (! function_exists('app')) {
/**
* Get the available container instance.
*
* @param string|null $abstract
* @param array $parameters
* @return mixed|\Illuminate\Contracts\Foundation\Application
*/
function app($abstract = null, array $parameters = [])
{
if (is_null($abstract)) {
return Container::getInstance();
}
return Container::getInstance()->make($abstract, $parameters);
}
}
Don't forget to add helpers.php
to the autoload
section of the composer.json
file:
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"YourApp\\": "src/"
}
}
Let's try it out.
<?php
// Same as Container::getInstance();
$container = app();
// Same as Container::getInstance()->make(Foo::class);
$foo = app(Foo::class);
Conveniently, illuminate/database
provides us with the Capsule\Manager class, designed for query builder integration outside of Laravel applications.
Let's start with the database connection configuration by creating the bootstrap.php
file with the following contents:
<?php
// https://getcomposer.org/doc/01-basic-usage.md#autoloading
require_once __DIR__.'/../vendor/autoload.php';
use Illuminate\Database\Capsule\Manager;
$manager = new Manager();
// Similar to https://github.com/laravel/laravel/blob/8.x/config/database.php#L36
$manager->addConnection([
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'database',
'username' => 'root',
'password' => 'password',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
]);
// This allows us to use static methods for database connections
$manager->setAsGlobal();
We can try querying the database now:
<?php
use Illuminate\Database\Capsule\Manager;
// https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Capsule/Manager.php#L191-L202
return Manager::table('orders')->get();
We can now send queries to the database using the query builder. But what about Eloquent ORM?
You can expect Eloquent ORM to work out of the box:
<?php
namespace YourApp\Models;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
protected $fillable = [
'name',
];
}
<?php
use YourApp\Models\Order;
Order::first();
We can use the schema method to obtain a Schema\Builder
instance used to compose migrations:
<?php
use Illuminate\Database\Capsule\Manager;
Manager::schema()->create('orders', function ($table) {
$table->bigIncrements('id');
$table->string('name');
$table->timestamps();
});
To run database migrations just execute the database migration file with php migration.php
.
Laravel already provides us with the dispatcher singleton, so we'll just register the service provider in the global container instance:
<?php
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Events\EventServiceProvider;
(new EventServiceProvider(app()))->register();
// Don't forget to bind 'events' alias to Dispatcher contract
app()->bind(Dispatcher::class, 'events');
To use illuminate/redis
you just need to bind the RedisManager instance to the redis
alias:
<?php
use Illuminate\Redis\RedisManager;
app()->bind('redis', function () {
// You can find information about available drivers here:
// https://github.com/laravel/framework/blob/8.x/src/Illuminate/Redis/RedisManager.php#L157-L176
return new RedisManager(app(), 'predis', [
'default' => [
'host' => '127.0.0.1',
'password' => null,
'port' => 6379,
'database' => 0
]
]);
});
Similarly to illuminate/database
, illuminate/queue
also provides us with the Capsule\Manager class. However, we still need to implement the queue:work
and queue:listen
commands ourselves.
Let's start with the Application contract.
In Laravel, this contract provides us with:
Our implementation (without the actual implements Application
syntax) of this contract will contain only one method - isDownForMaintenance
, that will always return false
, as our application won't need any maintenance (hopefully):
<?php
namespace YourApp;
use Illuminate\Container\Container;
class Application extends Container
{
public function isDownForMaintenance()
{
return false;
}
}
Example configuration for the database
driver:
<?php
use Illuminate\Queue\Capsule\Manager;
use Illuminate\Queue\Connectors\DatabaseConnector;
use Illuminate\Database\Capsule\Manager as DatabaseManager;
use Illuminate\Database\ConnectionResolver;
$queue = new Manager(app());
// Add the database driver configuration
$queue->addConnection([
'driver' => 'database',
'table' => 'jobs',
'connection' => 'default',
'queue' => 'default'
]);
$queue->setAsGlobal();
// Create new connection resolver, with the default database connection under the hood
$resolver = new ConnectionResolver([
'default' => DatabaseManager::connection()
]);
// Add database connector with resolver we created before
$queue->getQueueManager()
->addConnector('database', function () use ($resolver) {
return new DatabaseConnector($resolver);
});
Redis configuration example:
<?php
use Illuminate\Queue\Capsule\Manager;
$queue = new Manager(app());
$queue->addConnection([
'driver' => 'redis',
'connection' => 'default',
'queue' => 'default'
]);
$queue->setAsGlobal();
Now, we need to add the exception handler:
<?php
use Exception;
use Illuminate\Contracts\Debug\ExceptionHandler;
class Handler implements ExceptionHandler
{
public function report(Exception $e)
{
// Send something to Sentry, create an e-mail notification, etc.
}
// Request type is omitted, so 'illuminate/http' installation is not required
public function render($request, Exception $e)
{
// Render error for the web context
}
public function renderForConsole($output, Exception $e)
{
// Render error for the console context
}
public function shouldReport(Exception $e)
{
// Define if an error should be reported
}
}
app()->bind('exception.handler', function () {
return new Handler();
});
Finally, let's implement the queue:work
command:
<?php
require_once __DIR__.'/bootstrap/bootstrap.php';
use Illuminate\Queue\Worker;
use Illuminate\Queue\Capsule\Manager;
use Illuminate\Queue\WorkerOptions;
// Get the global queue manager instance
$queueManager = Manager::getQueueManager();
// Create new worker instance, with an event dispatcher and a custom exception handler
$worker = new Worker($queueManager, app('events'), app('exception.handler'));
$worker->daemon('default', 'default', new WorkerOptions());
To start a queue worker, use php worker.php
.
For cases when your code has memory leaks requires testing you can use the queue:listen
command.
Here, two PHP files are required, as queue job listener and executor are two different processes in this case.
listener.php
:
<?php
require_once __DIR__.'/bootstrap/bootstrap.php';
use Illuminate\Queue\Listener;
use Illuminate\Queue\ListenerOptions;
// In Laravel, by default, listener starts 'artisan' to execute new jobs,
// but in our case 'process.php' will take that role
// https://github.com/laravel/framework/blob/8.x/src/Illuminate/Queue/Listener.php#L72-L75
define('ARTISAN_BINARY', 'process.php');
$worker = app(Listener::class, [
// Both files are in the same folder, so we'll stick to __DIR__
'commandPath' => __DIR__
]);
// All 'process.php' output will be piped here
$worker->setOutputHandler(function ($type, $line) {
echo $line;
});
$worker->listen('default', 'default', new ListenerOptions());
process.php
:
<?php
require_once __DIR__.'/bootstrap/bootstrap.php';
use Illuminate\Queue\Worker;
use Illuminate\Queue\WorkerOptions;
use Illuminate\Queue\Capsule\Manager;
// Same as with 'queue:work', but instead of running a daemon process
// we'll just wait for one job, execute it, and pass control back to the 'listener.php' file
$queueWorker = Manager::getQueueWorker();
$worker = new Worker($queueManager, app('events'), app('exception.handler'));
$worker->runNextJob('default', 'default', new WorkerOptions());
You can start listening with php listener.php
.
Queueing new jobs looks like this:
<?php
use Illuminate\Queue\Capsule\Manager;
Manager::push(SomeJob::class);