slytherin
slytherin copied to clipboard
An extensible and SOLID-based PHP micro-framework.
Slytherin
Slytherin is a simple and extensible PHP micro-framework that tries to achieve a SOLID-based design for creating your next web application. It uses Composer as the dependency package manager to add, update or even remove external packages.
Installation
Install Slytherin
through Composer:
$ composer require rougin/slytherin
Basic Usage
Slytherin can be implemented in either the ContainerInterface
, IntegrationInterface
or a mixed of both:
Using the ContainerInterface
use App\Http\Controllers\WelcomeController;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Rougin\Slytherin\Application;
use Rougin\Slytherin\Container\Container;
use Rougin\Slytherin\Http\Response;
use Rougin\Slytherin\Http\ServerRequest;
use Rougin\Slytherin\Routing\Dispatcher;
use Rougin\Slytherin\Routing\DispatcherInterface;
use Rougin\Slytherin\Routing\Router;
// Define HTTP objects that is compliant to PSR-07 standards
$request = new ServerRequest((array) $_SERVER);
$response = new Response(http_response_code());
$router = new Router;
// Create a new route from WelcomeController class...
$router->get('/', WelcomeController::class . '@index');
// ...then define it to a dispatcher
$dispatcher = new Dispatcher($router);
// Add the above objects through a container
$container = new Container;
// Set the request as the PSR-07 server request instance
$container->set(ServerRequestInterface::class, $request);
// Set the response as the PSR-07 response instance
$container->set(ResponseInterface::class, $response);
// Set the dispatcher in the Routing\DispatcherInterface
$container->set(DispatcherInterface::class, $dispatcher);
// Lastly, run the application
(new Application($container))->run();
Using the IntegrationInterface
use App\Http\Controllers\WelcomeController;
use Rougin\Slytherin\Application;
use Rougin\Slytherin\Container\Container;
use Rougin\Slytherin\Http\HttpIntegration;
use Rougin\Slytherin\Integration\Configuration;
use Rougin\Slytherin\Routing\RoutingIntegration;
// Specify the integrations to be included and defined
$integrations = array(HttpIntegration::class);
$integrations[] = RoutingIntegration::class;
$router = new Router;
// Create a new route from the WelcomeController class
$router->get('/', WelcomeController::class . '@index');
// Supply values to integrations using a configuration file
$config = (new Configuration)->set('app.router', $router);
$config->set('app.http.server', (array) $_SERVER);
// Use the integrations and run the application
$app = new Application(new Container, $config);
$app->integrate((array) $integrations)->run();
Running the application using PHP's built-in web server:
$ php -S localhost:8000 index.php
Now open a web browser and access to http://localhost:8000.
Regarding the example implementation above, a chosen package must be implement with a provided interface in order for it to be integrated in Slytherin. More information about this can be found in the Using Interfaces section.
Packages
Slytherin also provide implementations on each component (container, dispatcher, etc.) that are built on top of other existing third-party packages. Kindly install their own respective dependencies first before using it directly. These packages can be located at the src directory.
Using Interfaces
The following items below are needed to run a Slytherin application:
- Container
- Stores the instances and dependences of the application for later use
- Package must be implemented in a PSR-11 standard
- Routing
- Dispatches defined routes to different handlers depending on rules that you have set up
- Package must be implemented in a
DispatcherInterface
- Http
- Provides a nice object-oriented interface for handling HTTP variables
- Package must be implemented in a PSR-07 standard
The following items below are not required but might be useful in the application development:
- Error Handler
- Detects and displays the error messages in a nice way
- Package must be implemented in an
ErrorHandlerInterface
- Middleware
- Template
- Even PHP has already a built-in template engine, it is much easier to separate the concerns between the data and the template
- Package must be implemented in a
RendererInterface
Including a package requires to use an interface to interact to the framework.
Integrating Packages
Integrating packages in Slytherin is simple, as long as the usage of interfaces that is introduced in the Using Interfaces section was understood.
Before proceeding on how to integrate packages, it is important to have a PSR-11 compliant container in order to resolve and store dependencies of a package.
Example
Twig is a flexible and secure template engine. To integrate it to Slytherin, the RendererInterface
must be implemented to the said package. An example of this implementation can be found here.
Notice that the Twig_Environment
is used as a dependency but according to the documentation, it is needed to be prepared first before it will be called as a dependency. In order to use the package globally in the whole application, a container must be used as mentioned earlier.
Components
Container
A container stores defined class objects that can be used later.
// Foo.php
class Foo
{
public function baz()
{
// ...
}
}
// Bar.php
class Bar
{
protected $foo;
public function __construct(Foo $foo)
{
$this->foo = $foo;
}
public function booz()
{
return $this->foo->baz();
}
}
This is the implementation for Bar
to have an instance of Foo
:
$foo = new Foo;
$bar = new Bar($foo);
To access the said class, it needs to be implemented again which is cumbersome to a developer. The solution for this is to store it into a container so it can be accessed anywhere.
use Rougin\Slytherin\Container\Container;
// ... Given that Foo and Bar classes were included
$container = new Container;
$container->set('Bar', $bar);
// Returns an instance of Bar
$new = $container->get('Bar');
The implementation above covers the basic functionality of a container. Some packages also provides additional functionalities like resolving dependencies and more. A list of packages that implement the dependency injection design pattern at can be seen at the awesome-php repository.
Example
To integrate a container to Slytherin, it must be implemented in the PSR-11 standard. An example of this implementation can be found here.
$container = new Rougin\Slytherin\Container\Container;
// Define your classes and dependencies here...
(new Rougin\Slytherin\Application($container))->run();
Error Handler
An error handler assists a developer in the detection and correction of errors in applications.
Errors are a good way to display what is wrong in an application, from a developer's perspective. However, it can be very dangerous to show a very detailed error message into production because it might get used as a information by hackers on how to gain access to the application. With this kind of vulnerability, error handling must be disabled in a production environment.
Example
Whoops is an error handler package. To integrate it to Slytherin, create a class and implement it to an ErrorHandlerInterface
. An example of this implementation can be found here.
use Rougin\Slytherin\Debug\ErrorHandlerInterface;
use Rougin\Slytherin\Debug\WhoopsErrorHandler;
$container = new Rougin\Slytherin\Container\Container;
$whoops = new WhoopsErrorHandler(new Whoops\Run);
$container->set(ErrorHandlerInterface::class, $whoops);
Routing
The Routing
component consists of a Dispatcher
and a Router
.
What is the difference between a Router and Dispatcher?
A
Dispatcher
uses the information from theRouter
to actually generate the resource. If theRouter
is asking for directions then the dispatcher is the actual process of following those directions. the dispatcher knows exactly what to create and the steps needed to generate the resource, but only after getting the directions from theRouter
. Basically, without theRouter
, theDispatcher
would not know what are the available routes that can be generated from.
Example
To integrate a route dispatcher to Slytherin, a package must be implemented both in DispatcherInterface
and RouterInterface
of the Routing component. An example implementation of both Dispatcher and Router are also provided by the framework.
use App\Http\Controllers\WelcomeController;
use Rougin\Slytherin\Container\Container;
use Rougin\Slytherin\Routing\Dispatcher;
use Rougin\Slytherin\Routing\DispatcherInterface;
use Rougin\Slytherin\Routing\Router;
$router = new Router;
// Create a new route from WelcomeController class...
$router->get('/', WelcomeController::class . '@index');
// ...then define it to a dispatcher
$dispatcher = new Dispatcher($router);
$container = new Container;
$container->set(DispatcherInterface::class, $dispatcher);
To dispatch a route, specify the HTTP method and the URI segment to the route dispatcher.
$dispatcher = $container->get(DispatcherInterface::class);
// Returns the result from WelcomeController::index
$dispatcher->dispatch('GET', '/');
HTTP
In PHP, there are built-in functions that can work in HTTP. The list of functions that are related to it can be found here. However, a HTTP package with a nice object-oriented interface is greatly recommended.
To integrate a HTTP component to Slytherin, a package must implement both ServerRequestInterface
and ResponseInterface
interfaces as defined in PSR-07 standard. A meta document about the implementation can be found here.
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Rougin\Slytherin\Container\Container;
use Rougin\Slytherin\Http\Response;
use Rougin\Slytherin\Http\ServerRequest;
$request = new ServerRequest($_SERVER, $_COOKIE);
$container = new Container;
$container->set(ServerRequestInterface::class, $request);
$response = new Response(http_response_code());
$container->set(ResponseInterface::class, $response);
Why need to implement two classes for a HTTP component if it can be in a one cool class? They are needed to be separated because of the Single Responsibility Principle.
In object-oriented programming, the said principle states that every class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility. With that principle, it's easier for you to debug a certain problem in a class because you can easily determine the responsibility that is included to it.
Changelog
Please see CHANGELOG for more information what has changed recently.
Testing
$ composer require filp/whoops league/container nikic/fast-route phroute/phroute rdlowrey/auryn twig/twig zendframework/zend-diactoros zendframework/zend-stratigility --dev
$ composer test
Credits
- All contributors
- Awesome PHP!
- Codeigniter
- Crux anti-framework
- Fucking Small
- Laravel framework
- No Framework Tutorial
- PHP Design Patterns
- Symfony framework
License
The MIT License (MIT). Please see LICENSE for more information.