arnapou / psr
Library - KISS PSR classes.
Requires
- php: ~8.3.0
- nyholm/psr7: ^1.8
- nyholm/psr7-server: ^1.1
- psr/cache: ^3.0
- psr/clock: ^1.0
- psr/container: ^2.0
- psr/event-dispatcher: ^1.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/link: ^2.0
- psr/log: ^3.0
- psr/simple-cache: ^3.0
- symfony/http-client: ^5.4
Requires (Dev)
- ext-memcached: *
- ext-pcntl: *
- ext-redis: *
- friendsofphp/php-cs-fixer: ^3.40
- phpstan/phpstan: ^1.10
- phpunit/php-code-coverage: ^10.1
- phpunit/phpunit: ^10.5
README
KISS (Keep It Simple Stupid) PSR (PHP Standards Recommendations) classes.
Installation
composer require arnapou/psr
packagist 👉️ arnapou/psr
When it is worth to use this library
- you need simple decorators, proxies, adapters, ... about PSR's
- you need simple implementations covering the basics
Example PSR-3 Logger
File (rotation optional, inject your custom formatter or PSR-20 Clock Interface)
$logger = new \Arnapou\Psr\Psr3Logger\FileLogger(
logDirectory: '/path/of/logs',
logName: 'app.log',
rotation: \Arnapou\Psr\Psr3Logger\Utils\Rotation::Every12Hours,
);
Decorators
$logger = new \Arnapou\Psr\Psr3Logger\Decorator\FixedLevelLogger($decorated, 'info');
$logger = new \Arnapou\Psr\Psr3Logger\Decorator\MinimumLevelLogger($decorated, 'info');
$logger = new \Arnapou\Psr\Psr3Logger\Decorator\PrefixLogger($decorated, '[My-App]');
// The ContextLogger helps to gather the same context for a bunch of logs (DRY).
// It avoids repeating the context array everywhere.
$logger = new \Arnapou\Psr\Psr3Logger\Decorator\ContextLogger($decorated);
$logger->addContext(['user_id' => 42, 'name' => 'John Doe']);
$logger->debug('Hello');
$logger->info('World');
Example PSR-4 Autoloading
If you use composer, you basically don't need that, but in small edge cases, it can be useful.
$autoloader = new \Arnapou\Psr\Psr4Autoloader\Autoloader();
$autoloader->addNamespace('MyVendor/MyProject', '/path/of/src/for/example');
$autoloader->register();
Example PSR-6 Cache
KISS, here we only have PSR-6 adapter of PSR-16 Simple cache.\ Note that you can inject your own PSR-20 Clock Interface.
$cache = new \Arnapou\Psr\Psr6Cache\CacheItemPool(
psr16SimpleCache: $psr16SimpleCache,
defaultTtl: 900,
);
$item = $cache->getItem('my-key');
$item->set('my-value');
$item->expiresAfter(300);
$cache->save($item);
Example PSR-7 Http Message
We don't have our own PSR-7 classes, only decorators.
The project uses nyholm/psr7 which a super lightweight PSR-7 implementation, strict and fast.\ Reinventing the wheel here does not bring any simplicity.
Decorator for send (and more)
$response = new \Arnapou\Psr\Psr7HttpMessage\Response($decoratedPsr7Response);
$response->withHeader(\Arnapou\Psr\Psr7HttpMessage\Header\CacheControl::sharedMaxAge(600))
$response->send();
Example PSR-11 Container
Service Locator
$container = new \Arnapou\Psr\Psr11Container\ServiceLocator();
$container->registerFactory(
'db',
static function() {
$pdo = new PDO($dsn, $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $pdo;
}
);
$container->get('db')->query('SELECT ....');
Static Services
/**
* @method static \PDO db()
*/
class Services extends \Arnapou\Psr\Psr11Container\StaticServices
{
public function getFactories(): iterable
{
yield 'db' => static function() {
$pdo = new PDO($dsn, $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $pdo;
};
}
}
Services::db()->query('SELECT ....');
Example PSR-13 Link
To be honest, I don't know small projects where it makes a difference using this PSR.
Basic objects
$link = new \Arnapou\Psr\Psr13Link\Link('https://arnapou.net/');
$link = new \Arnapou\Psr\Psr13Link\EvolvableLink('https://arnapou.net/');
Example PSR-14 Event Dispatcher
The way we can implement the PSR is very wide.
I basically built some "object bus" with routing of events based on php type hinting.\
In the PSR, an event can be any object, you can stop the propagation implementing a StoppableEventInterface
for that object.
This is very simple but efficient and intuitive.
$dispatcher = new \Arnapou\Psr\Psr14EventDispatcher\ClassEventDispatcher();
$dispatcher->listen(function(DateTime $date) {
echo "DateTime: " . $date->format('Y-m-d') . "\n";
});
$dispatcher->listen(function(DateTimeImmutable $date) {
echo "DateTimeImmutable: " . $date->format('Y-m-d') . "\n";
});
$dispatcher->listen(function(DateTimeInterface $date) {
echo "DateTimeInterface: " . $date->format('Y-m-d') . "\n";
});
$dispatcher->dispatch(new DateTime('2023-10-10'));
// This will echo
// DateTime: 2023-10-10
// DateTimeInterface: 2023-10-10
Php Handlers : in order to ease the logging of php crashs, etc ... I made a class which uses my dispatcher
$phpHandlers = new \Arnapou\Psr\Psr14EventDispatcher\PhpHandlers($logger);
$phpHandlers->registerAll();
// You can decide to throw ErrorException on notices
$phpHandlers->throwErrorNotice();
// You can hook to do what you want on exceptions or errors
$phpHandlers->listen(function (\Throwable $throwable) {
if ($throwable->getCode() === 666) {
exit;
}
});
// Or on shell signals
$phpHandlers->listen(function (\Arnapou\Psr\Psr14EventDispatcher\Event\SignalEvent $event) use ($logger) {
if ($event->value === SIGINT) {
$logger->emergency('Somebody CTRL-C the process');
}
});
// Or on process exit, you also can carry your own infos in the object used
$phpHandlers->shutdownEvent->addValue('AppName', 'my-app');
$phpHandlers->listen(function (\Arnapou\Psr\Psr14EventDispatcher\Event\ShutdownEvent $event) use ($logger) {
$appName = $event->getValues()['AppName'];
$logger->debug('The process elapsed '. $event->getElapsedTime() . ' seconds for app ' . $appName);
});
Example PSR-15 Http Handlers
The way we can implement the PSR is very wide.
I basically built a "route handler" which uses internally my PSR-14 Event Dispatcher.\ I should not have to tell you that this is enough simple for small projects without hundreds of routes.\ If you need performance in your big project, you probably don't need this stuff.
This is very simple and 100% PSR dependant.
$handler = new \Arnapou\Psr\Psr15HttpHandlers\HttpRouteHandler();
// Add routes
$handler
->addRoute(
'/users/{id}',
function (int $id) {
return ['user'=> $id]
}
)
->setRequirement('id', '\d+');
// Get the server Request (here from global by lazyness)
$request = (new \Arnapou\Psr\Psr17HttpFactories\HttpFactory())->createServerRequestFromGlobals();
// Handle and get the response, a NoResponseFound will be thrown if no match
try {
$response = $handler->handle($request);
} catch (\Arnapou\Psr\Psr15HttpHandlers\Exception\NoResponseFound) {
$response = new \Arnapou\Psr\Psr7HttpMessage\HtmlResponse('Not Found', 404);
}
// Use the response decorator to send it
(new \Arnapou\Psr\Psr7HttpMessage\Response($response))->send();
Example PSR-16 Simple Cache
There is one thing I didn't implement from the PSR : it is the PSR key characters restriction.\ Because I never saw a case during the past 12 years where it was necessary.\ The only restrictions implemented are technically (ex memcached).
Implementations.
// For runtime, full memory
$cache = new \Arnapou\Psr\Psr16SimpleCache\ArraySimpleCache(defaultTtl: 900);
// Flat file cache with custom serializer and PSR-20 Clock Interface
$cache = new \Arnapou\Psr\Psr16SimpleCache\FileSimpleCache(path: '/path/of/cache', defaultTtl: 900);
// Memcached cache with custom serializer and PSR-20 Clock Interface
$cache = new \Arnapou\Psr\Psr16SimpleCache\MemcachedSimpleCache($memcached);
// Redis cache with custom serializer and PSR-20 Clock Interface
$cache = new \Arnapou\Psr\Psr16SimpleCache\RedisSimpleCache($redis);
// Does nothing at all
$cache = new \Arnapou\Psr\Psr16SimpleCache\NullSimpleCache();
Decorators.
// Helps to reduce I/Os on decorated cache by keeping in memory the values already retrieved
$cache = new \Arnapou\Psr\Psr16SimpleCache\Decorated\RuntimeSimpleCache($decorated);
// Helps to isolate keys in "namespaces" for instance
$cache = new \Arnapou\Psr\Psr16SimpleCache\Decorated\PrefixSimpleCache($decorated, 'some:prefix');
// Forces the ttl to be in the range wanted to avoid silly TTLs
$cache = new \Arnapou\Psr\Psr16SimpleCache\Decorated\TtlRestrictedSimpleCache($decorated, ttlMin: 60, ttlMax: 3600);
// Avoid any writing in that cache
$cache = new \Arnapou\Psr\Psr16SimpleCache\Decorated\ReadonlySimpleCache($decorated, throwException: true);
Example PSR-17 Http Factory
The project uses nyholm/psr7 which a super lightweight PSR-7 implementation, strict and fast.\ Reinventing the wheel here does not bring any simplicity.
Our PSR-17 class is a composition over nyholm/psr7 factory.
$factory = new \Arnapou\Psr\Psr17HttpFactories\HttpFactory();
$serverRequest = $factory->createServerRequestFromGlobals();
Example PSR-18 Http Client
The project uses symfony/http-client which is one of the best client while not being too heavy like other whales.\ Reinventing the wheel here does not bring any simplicity.
Our PSR-18 class is a composition over symfony/http-client and nyholm/psr7 factory.
$client = new \Arnapou\Psr\Psr18HttpClient\HttpClient();
$request = $client->createRequest('GET', 'https://arnapou.net/ip');
$response = $client->sendRequest($request);
echo $response->getBody();
Example PSR-20 Clock Interface
One of the most simple PSR, but very powerful.\ This repo uses this clock interface in loggers and caches.\ It helps a lot for testing for example.
// Basic clock with date and timezone
$clock = new \Arnapou\Psr\Psr20Clock\NowClock();
echo $clock->now()->format('Y-m-d H:i:s') . "\n";
// This clock will always have the same "time".
$clock = \Arnapou\Psr\Psr20Clock\FixedClock::createFromTimestamp(time());
echo $clock->now()->format('Y-m-d H:i:s') . "\n";
Changelog versions
Start | Tag, Branch | Php |
---|---|---|
25/11/2023 | 2.x, main | 8.3 |
12/09/2023 | 1.x | 8.2 |