Commit 6e6026d5 authored by Jordan Breton's avatar Jordan Breton

added queries base

parent e976758f
......@@ -12,7 +12,7 @@ use wfw\engine\core\app\factory\IGenericAppFactory;
use wfw\engine\core\cache\ICacheSystem;
use wfw\engine\core\cache\systems\APCUBasedCache;
use wfw\engine\core\command\CommandHandlerFactory;
use wfw\engine\core\command\ICommandBus;
use wfw\engine\core\command\IQueryProcessor;
use wfw\engine\core\command\ICommandHandlerFactory;
use wfw\engine\core\command\ICommandInflector;
use wfw\engine\core\command\inflectors\NamespaceBasedInflector;
......@@ -173,7 +173,7 @@ class BaseAppContext implements IAppContext {
'instanceOf' => MailFactory::class,
'shared' => true
],
ICommandBus::class => [ 'instanceOf' => SynchroneCommandBus::class, 'shared' => true ],
IQueryProcessor::class => [ 'instanceOf' => SynchroneCommandBus::class, 'shared' => true ],
ICommandInflector::class => [
'instanceOf' => NamespaceBasedInflector::class, 'shared' => true,
'constructParams' => [ $this->getCommandHandlers() ]
......
......@@ -7,7 +7,7 @@ use wfw\engine\core\command\security\ICommandSecurityCenter;
/**
* Traite les commandes de manières synchrone
*/
final class SynchroneCommandBus implements ICommandBus {
final class SynchroneCommandBus implements IQueryProcessor {
/** @var ICommandInflector $_inflector */
private $_inflector;
/** @var ICommandSecurityCenter $_security */
......
......@@ -76,12 +76,17 @@ final class NamespaceBasedInflector implements ICommandInflector {
$res[] = $this->_handlers[$command];
}else{
$r = [];
$tmp = explode("\\",$command);
$className = array_pop($tmp);
if ($pos = strrpos($command, $search = "\\commands\\") !== false) {
$handlerClass = substr_replace(
$command,
"\\commands\\handlers\\",
$pos,
strlen($search)
);
}
try{
$r[] = $this->_factory->buildCommandHandler(
implode('\\',$tmp).'\\handlers\\'.$className.'Handler'
($handlerClass ?? $command)."Handler"
);
}catch(\Exception $e){}
......
<?php
namespace wfw\engine\core\query;
use wfw\engine\lib\PHP\types\UUID;
/**
* Command
*/
interface IQuery {
/**
* @return UUID
*/
public function getId():UUID;
/**
* @return null|string Uner that initates the query (if available)
*/
public function getInitiatorId():?string;
/**
* @return float
*/
public function getGenerationDate():float;
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query;
use wfw\engine\core\query\errors\NoQueryHandlerFound;
/**
* Dispatcher de commandes
*/
interface IQueryDispatcher {
/**
* @param IQuery $command Commande à dispatcher
* @throws NoQueryHandlerFound
*/
public function dispatchCommand(IQuery $command):void;
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query;
/**
* Permet de traiter une commande
*/
interface IQueryHandler {
/**
* Traite la commande
* @param IQuery $command Commande à traiter
*/
public function handleCommand(IQuery $command);
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query;
/**
* Factory de IQueryHandler
*/
interface IQueryHandlerFactory {
/**
* Constuit un IQueryHandler à partir du nom de la classe d'un QueryHandler
*
* @param string $handlerClass Classe du handler à construire
* @param array $params Paramètres de création
* @return IQueryHandler
*/
public function buildQueryHandler(string $handlerClass, array $params=[]):IQueryHandler;
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query;
use wfw\engine\core\query\errors\NoQueryHandlerFound;
/**
* Permet de trouver un handler pour une commande
*/
interface IQueryInflector {
/**
* Trouve un handler pour une commande
*
* @param IQuery $query Query dont on cherche le handler
* @return IQueryHandler[]
* @throws NoQueryHandlerFound
*/
public function resolveQueryHandlers(IQuery $query):array;
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query;
/**
* Reçois les commandes et les redirige vers leur handler
*/
interface IQueryProcessor {
/**
* Redirige la commande vers son handler
*
* @param ICommand $command Commande à rediriger
*
* @return mixed
*/
public function executeCommand(ICommand $command):void;
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query;
/**
* Utilise un inflecteur pour déterminer le command handler à charger, évite les configurations
* manuelles.
*/
final class InflectorBasedQueryDispatcher implements IQueryDispatcher {
/** @var ICommandInflector $_inflector */
private $_inflector;
/**
* InflectorBasedCommandObserver constructor.
*
* @param ICommandInflector $inflector Inflecteur
*/
public function __construct(ICommandInflector $inflector) {
$this->_inflector = $inflector;
}
/**
* @param ICommand $command Commande à dispatcher
* @throws NoHandlerFound
*/
public function dispatchCommand(ICommand $command): void {
$handlers = $this->_inflector->resolveCommandHandlers($command);
foreach($handlers as $handler){
$handler->handleCommand($command);
}
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query;
use wfw\engine\lib\PHP\types\UUID;
/**
* Commande de base
*/
abstract class Query implements IQuery {
/** @var UUID */
private $_uuid;
/** @var null|string $_userId */
private $_initiatorId;
/** @var float */
private $_generationDate;
/**
* Command constructor.
*
* @param null|string $initiatorId
*/
public function __construct(?string $initiatorId=null){
$this->_initiatorId = $initiatorId;
$this->_uuid = new UUID();
$this->_generationDate = microtime(true);
}
/**
* @return UUID Command ID
*/
public function getId():UUID{
return $this->_uuid;
}
/**
* @return null|string User ID that try to execute the command
*/
public function getInitiatorId(): ?string {
return $this->_initiatorId;
}
/**
* @return float Creation date
*/
public function getGenerationDate():float{
return $this->_generationDate;
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query;
use wfw\engine\core\query\errors\NoQueryHandlerFound;
/**
* Dispatcher de query base. Peut-être construit avec une liste de dispatchers de queries.
* L'instance courante tentera un à un chaque dispatcher dans l'ordre à chaque echec, et s'arrêtera
* dés lors qu'un dispatch() aura réussi.
*/
final class QueryDispatcher implements IQueryDispatcher {
/** @var IQueryDispatcher[] $_dispatchers */
private $_dispatchers;
/**
* QueryDispatcher constructor.
*
* @param IQueryDispatcher ...$dispatchers
*/
public function __construct(IQueryDispatcher ...$dispatchers) {
$this->_dispatchers = $dispatchers;
}
/**
* @param IQuery $command Commande à dispatcher
* @throws NoQueryHandlerFound
*/
public function dispatchCommand(IQuery $command): void {
$success = false;
foreach ($this->_dispatchers as $dispatcher){
try{
$dispatcher->dispatchCommand($command);
$success = true;
break;
}catch(NoQueryHandlerFound $e){}
}
if(!$success){
throw new NoQueryHandlerFound("No handler found for command ".get_class($command));
}
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query;
use wfw\engine\core\app\factory\IGenericAppFactory;
/**
* Factory basée sur dice pour la création d'un command handler
*/
final class QueryHandlerFactory implements IQueryHandlerFactory {
/** @var IGenericAppFactory $_factory */
private $_factory;
/**
* DiceBasedCommandHandlerFactory constructor.
*
* @param IGenericAppFactory $factory
*/
public function __construct(IGenericAppFactory $factory) {
$this->_factory = $factory;
}
/**
* Constuit un IQueryHandler à partir du nom de la classe d'un CommandHandler
*
* @param string $handlerClass Classe du handler à construire
* @param array $params Paramètres de création
* @return IQueryHandler
*/
public function buildCommandHandler(string $handlerClass, array $params=[]): IQueryHandler {
return $this->_factory->create($handlerClass,$params,[IQueryHandler::class]);
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query;
use wfw\engine\core\query\security\errors\RejectedQuery;
use wfw\engine\core\query\security\IQuerySecurityCenter;
/**
* Traite les commandes de manières synchrone
*/
final class QueryProcessor implements IQueryProcessor {
/** @var IQueryInflector $_inflector */
private $_inflector;
/** @var IQuerySecurityCenter $_security */
private $_security;
/**
* SynchroneCommandBus constructor.
*
* @param IQueryInflector $inflector Trouve le handler d'une commande
* @param IQuerySecurityCenter $security
*/
public function __construct(IQueryInflector $inflector, IQuerySecurityCenter $security) {
$this->_inflector = $inflector;
$this->_security = $security;
}
/**
* Redirige la commande vers son handler et retourne le résultat du handler
* @param ICommand $command Commande à rediriger
*/
public function executeCommand(ICommand $command):void {
if(!$this->_security->allowCommand($command)) throw new RejectedQuery(
"Access denied : ".get_class($command)." rejected by the security center."
);
$handlers = $this->_inflector->resolveCommandHandlers($command);
foreach($handlers as $handler){
$handler->handleCommand($command);
}
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query\errors;
/**
* Aucun handler n'a été trouvé pour la commande.
*/
final class NoQueryHandlerFound extends \Exception {}
\ No newline at end of file
<?php
namespace wfw\engine\core\query\errors;
/**
* Une commande a échoué
*/
class QueryFailure extends \Exception {}
\ No newline at end of file
<?php
namespace wfw\engine\core\query\inflectors;
use wfw\engine\core\query\errors\NoQueryHandlerFound;
use wfw\engine\core\query\IQuery;
use wfw\engine\core\query\IQueryHandler;
use wfw\engine\core\query\IQueryHandlerFactory;
use wfw\engine\core\query\IQueryInflector;
/**
* Tente de résoudre les QueryHandlers en se basant sur le namespace de la querye.
* Exemple : la query \wfw\engine\packages\users\queries\CreateUser
* sera résolue : \wfw\engine\packages\users\queries\handlers\CreateUserHandler
*/
final class NamespaceBasedInflector implements IQueryInflector {
/** @var IQueryHandlerFactory $_factory */
private $_factory;
/** @var IQueryHandler[][] $_handlers */
private $_handlers;
/** @var IQueryHandler $_resolved */
private $_resolved;
/**
* NamespaceBasedInflector constructor.
*
* @param IQueryHandlerFactory $factory Factory de handlers
* @param array $handlers
*/
public function __construct(IQueryHandlerFactory $factory, array $handlers = []) {
$this->_factory = $factory;
$this->_handlers = [];
$this->_resolved = [];
foreach ($handlers as $queryClass => $handlerClasses){
if(!is_a($queryClass,IQuery::class,true))
throw new \InvalidArgumentException(
"$queryClass doesn't implements ".IQuery::class
);
if(!isset($this->_handlers[$queryClass])) $this->_handlers[$queryClass] = [];
foreach($handlerClasses as $class=>$params){
if(!is_a($class,IQueryHandler::class,true))
throw new \InvalidArgumentException(
"$class doesn't implements ".IQueryHandler::class
);
$this->_handlers[$queryClass][$class] = $factory->buildQueryHandler(
$class,$params
);
}
}
}
/**
* Trouve un handler pour une querye
*
* @param IQuery $query Comande dont on cherche le handler
* @return IQueryHandler[]
* @throws NoQueryHandlerFound
*/
public function resolveQueryHandlers(IQuery $query): array {
$handlers = $this->resolveHandlersFromQueryClass(get_class($query));
if(count($handlers)>0){
return $handlers;
}else{
throw new NoQueryHandlerFound(
"No query handler found for query ".get_class($query)
);
}
}
/**
* @param string $query Classe de la querye à résoudre.
* @return array
*/
private function resolveHandlersFromQueryClass(string $query): array {
$res = [];
if(isset($this->_resolved[$query])){
$res[] = $this->_handlers[$query];
}else{
$r = [];
if ($pos = strrpos($query, $search = "\\queries\\") !== false) {
$handlerClass = substr_replace(
$query,
"\\queries\\handlers\\",
$pos,
strlen($search)
);
}
try{
$r[] = $this->_factory->buildQueryHandler(
($handlerClass ?? $query)."Handler"
);
}catch(\Exception $e){}
$this->_handlers[$query] = array_merge(
$this->_handlers[$query] ?? [],
$r
);
$res[] = $r;
}
foreach($this->_handlers as $class=>$handlers){
if(is_a($query,$class)) $res[] = $handlers;
}
return array_merge(...$res);
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query\security;
use wfw\engine\core\query\security\rules\IQueryAccessRule;
/**
* Allow to create access rules
*/
interface IQueryAccessRuleFactory {
/**
* @param string $ruleClass
* @param array $params
* @return IQueryAccessRule
*/
public function create(string $ruleClass,array $params=[]):IQueryAccessRule;
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query\security;
use wfw\engine\core\query\IQuery;
/**
* Will check if a user (or anonymous user) is allowed to run a command.
*/
interface IQuerySecurityCenter {
/**
* @param IQuery $cmd Command to run.
* @return bool True, the command is allowed for that user. False otherwise.
*/
public function allowCommand(IQuery $cmd):bool;
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query\security;
use wfw\engine\core\app\factory\IGenericAppFactory;
use wfw\engine\core\query\security\rules\IQueryAccessRule;
/**
* Create access rules from a class and params with the app DIC.
*/
final class QueryAccessRuleFactory implements IQueryAccessRuleFactory {
/** @var IGenericAppFactory $_factory */
private $_factory;
/**
* QueryAccessRuleFactory constructor.
*
* @param IGenericAppFactory $factory
*/
public function __construct(IGenericAppFactory $factory) {
$this->_factory = $factory;
}
/**
* @param string $ruleClass
* @param array $params
* @return IQueryAccessRule
*/
public function create(string $ruleClass, array $params = []): IQueryAccessRule {
return $this->_factory->create($ruleClass,$params,[IQueryAccessRule::class]);
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query\security;
use wfw\engine\core\query\IQuery;
use wfw\engine\core\query\security\rules\IQueryAccessRule;
/**
* Default security center. Will check if a command is allowed checking a CommandAccessRule.
*/
final class QuerySecurityCenter implements IQuerySecurityCenter {
/** @var IQueryAccessRule $_rule */
private $_rule;
/** @var bool $_ignoredAsTrue */
private $_ignoredAsTrue;
/**
* QuerySecurityCenter constructor.
*
* @param IQueryAccessRule $rule
* @param bool $ignoredAsTrue
*/
public function __construct(IQueryAccessRule $rule, bool $ignoredAsTrue = false) {
$this->_rule = $rule;
$this->_ignoredAsTrue = $ignoredAsTrue;
}
/**
* @param IQuery $cmd Query to run.
* @return bool True, the query is allowed for that user. False otherwise.
*/
public function allowCommand(IQuery $cmd): bool {
return $this->_rule->checkCommand($cmd) ?? $this->_ignoredAsTrue;
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query\security\errors;
use wfw\engine\core\query\errors\QueryFailure;
/**
* Throwed when a command have been rejected (attempted to be runned by a non authorized user)
*/
class RejectedQuery extends QueryFailure {}
\ No newline at end of file
<?php
namespace wfw\engine\core\query\security\rules;
use wfw\engine\core\query\IQuery;
/**
* All queries are allowed.
*/
final class AllQueriesAllowed implements IQueryAccessRule{
/**
* @param IQuery $cmd
* @return null|bool True if the query can be run, false otherwise.
*/
public function checkQuery(IQuery $cmd): ?bool {
return true;
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query\security\rules;
use wfw\engine\core\query\IQuery;
/**
* Will deny all querys.
*/
final class AllQueriesDenied implements IQueryAccessRule {
/**
* @param IQuery $cmd
* @return null|bool True if the query can be run, false otherwise.
*/
public function checkQuery(IQuery $cmd): ?bool {
return false;
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query\security\rules;
use wfw\engine\core\query\IQuery;
/**
* If any of the given rules return false, the result is false.
*/
class AndQueryAccessRule implements IQueryAccessRule{
/** @var IQueryAccessRule[] $_rules */
private $_rules;
/**
* AndQueryAccessRule constructor.
*
* @param IQueryAccessRule ...$rules
*/
public function __construct(IQueryAccessRule ...$rules) {
$this->_rules = $rules;
}
/**
* @param IQuery $cmd
* @return null|bool True if the query can be run, false otherwise.
*/
public function checkQuery(IQuery $cmd): ?bool {
if(count($this->_rules) === 0) return null;
$unapplicable = 0;
foreach($this->_rules as $rule){
if(!$res = $rule->checkQuery($cmd)){
if(is_null($res)) $unapplicable++;
else return false;
}
}
if($unapplicable === count($this->_rules)) return null;
return true;
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query\security\rules;
use wfw\engine\core\query\IQuery;
/**
* Rule to check if a couple query is allowed.
*/
interface IQueryAccessRule {
/**
* @param IQuery $cmd
* @return null|bool True if the query can be run, false otherwise. Null if not applicable
*/
public function checkQuery(IQuery $cmd):?bool;
}
\ No newline at end of file
<?php
namespace wfw\engine\core\query\security\rules;
/**
* Check for rules to apply to commands
*/
interface IQueryAccessRulesCollector {