cevantime / sherpa-core
Sherpa Core
Requires
- php: >=7.2
- doctrine/cache: ~1.4
- php-di/php-di: ^6.0
- psr/http-message: ~1.0
- psr/http-server-handler: ~1.0
- psr/http-server-middleware: ~1.0
- symfony/event-dispatcher: ^4.0
- zendframework/zend-diactoros: ~1.0
Requires (Dev)
- codeception/codeception: ^2.4
- phpunit/phpunit: ~7.0
This package is auto-updated.
Last update: 2025-03-29 00:31:43 UTC
README
A basic core class to build psr compliant applicationswith ease. The code is intentionnally minimal. For a more integrated experience, see Sherpa Framework.
Installation
Sherpa Core is installable via composer
composer require cevantime/sherpa
Getting Started
Sherpa use psr-7 for HTTP messages. You can use Zend Diactoros implementation to build your Request in your index file.
// index.php use Zend\Diactoros\ServerRequestFactory; $request = ServerRequestFactory::fromGlobals();
Now, you can init the Sherpa Kernel
use Sherpa\Kernel\Kernel; // ... $app = new Kernel();
Now, you can add a little middleware to display a simple 'Hello Sherpa' :
use Zend\Diactoros\Response\HtmlResponse; // ... $app->pipe(function(){ return new HtmlResponse("Hello Sherpa !"); });
The Kernel will handle the response for the request
$response = $app->handle($request);
When the response is ready, it needs to be emitted !
use Zend\Diactoros\Response\SapiEmitter; // ... (new SapiEmitter())->emit($response);
That's it ! Now visit your index file. You should see "Hello Sherpa !"
Middlewares
Sherpa Core do his best to be compliant with psr7 and psr-15 recommandations. Those recommendations introduces the usage of Message Interface and Middlewares. Piping middlewares in the Kernel is pretty simple :
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; $kernel->pipe(function(ServerRequestInterface $request, RequestHandlerInterface $handler) { // ... });
Accordingly to the specifications, middlewares must return an instance of Psr\Http\Message\ResponseInterface
. It can do so by :
- Delegating the Response creation to the next middleware by calling
$handler->handle($request)
and eventually modify the response. - Generate his own Response and bypass the next Middlewares.
Here is a (imaginary) examplen that check if we are visiting an admin uri and prevent user that are not admin to enter !
$kernel->pipe(function(ServerRequestInterface $request, RequestHandlerInterface $handler) { // check if user tries to access an admin page and check if he is admin if(preg_match($request->getUri()->getPath(), '~^/admin~') && ! $request->getAttribute('user')->isAdmin()) { // if not, send an error return (new Response('php://memory')) ->withStatus(403, 'You are not allowed to enter !'; } // otherwise, let the process continue and the next middleware proceed return $handler->handle($request); });
Optionnally (but it is worth noting it !) an integer can be provided as second param in order to set a priority for the middleware (higher comes before).
$kernel->pipe(function(){ return new Response(...); }, 50);
Container and Autowiring
Sherpa Core use PHP DI Container as a Dependency Container and Dependency Injector. This package lets you inject any class of your project (even your vendor) with minimal configuration. Every type hinted parameter can be injected. You only need to configure parameters than can't be guessed by type hinting. Classes that are defined using class/interface names as keys will become injectable :
use function DI\get; use function DI\create; use Psr\Log\LoggerInterface; use Monolog\Logger; use Monolog\Handler\StreamHandler; use Psr\Container\ContainerInterface; //... $builder = $kernel->getContainerBuilder(); $builder->addDefinitions([ // create definition directly... 'log.folder' => 'var/log', // ... or using a callback function... StreamHandler::class => function(ContainerInterface $container) { return new StreamHandler($container->get('log.folder'); }, // ... or using configuration methods that come with php di LoggerInterface::class => create(Logger::class) ->constructor('mylogger') ->method('pushHandler', get(StreamHandler::class)) ]);
Now a logger is injectable anywhere, including a class middleware :
use App\UserRepository; use Psr\Log\LoggerInterface; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\RequestHandlerInterface as Handler; use Psr\Http\Message\ResponseInterface as Response; class UserMiddleware implements MiddlewareInterface { protected $userRepository; protected $logger; /** * @param UserRepository $userRepository is auto-injected ! * @param LoggerInterface $logger hase been configure to inject an instance of Logger * / public function __construct(UserRepository $userRepository, LoggerInterface $logger) { $this->userRepository = $userRepository; $this->logger = $logger; } public function process(Request $request, Handler $handler): Response { $user = $this->userRepository->findBy(['id' => $_SESSION['user_id'] ?? 0]); if($user) { $request->setAttribute('user', $user); } else { $this->logger->log('info', 'No user found in session'); } return $handler->handle($request); } }
The Kernel will use di container to inject the middleware in the middleware stack. Thus you can pipe middlewares by only providing their class names.
$kernel->pipe(UserMiddleware::class);
Conclusion
This kernel is fairly minimal but it is a good starting point to build great applications with full psr compliance. Sherpa Framework is an example of how this kernel can be used.