Commit 02250247 authored by Jordan Breton's avatar Jordan Breton

WIP modules

Added support for passing constructor params in models, domainEventListeners and CommandHandlers configuration files.
Added base support for regional langs (fr_FR, en_US,...) Trads must be done
.
parent 7b5d91a5
<?php
use wfw\engine\core\conf\IModuleDescriptor;
use wfw\engine\core\conf\WFWModulesCollector;
WFWModulesCollector::collectModules();
return array_merge(
require dirname(__DIR__,3)."/engine/config/default.command.handlers.php",
require dirname(__DIR__)."/site.command.handlers.php"
require dirname(__DIR__,3)."/engine/config/default.command.handlers.php",
require dirname(__DIR__)."/site.command.handlers.php",
...array_map(
function($module){
if(is_a((string) $module, IModuleDescriptor::class, true)){
/** @var IModuleDescriptor $module */
return $module::commandHandlers();
}else return [];
}, WFWModulesCollector::modules()
)
);
\ No newline at end of file
<?php
use wfw\engine\core\conf\IModuleDescriptor;
use wfw\engine\core\conf\WFWModulesCollector;
WFWModulesCollector::collectModules();
return array_merge(
require dirname(__DIR__,3)."/engine/config/default.domain_events.listeners.php",
require dirname(__DIR__)."/site.domain_events.listeners.php"
require dirname(__DIR__,3)."/engine/config/default.domain_events.listeners.php",
require dirname(__DIR__)."/site.domain_events.listeners.php",
...array_map(
function($module){
if(is_a((string) $module, IModuleDescriptor::class, true)){
/** @var IModuleDescriptor $module */
return $module::domainEventListeners();
}else return [];
}, WFWModulesCollector::modules()
)
);
\ No newline at end of file
<?php
use wfw\engine\core\conf\IModuleDescriptor;
use wfw\engine\core\conf\WFWModulesCollector;
WFWModulesCollector::collectModules();
return array_merge(
require dirname(__DIR__,3)."/engine/config/default.models.php",
require dirname(__DIR__)."/site.models.php"
require dirname(__DIR__)."/site.models.php",
...array_map(
function($module){
if(is_a((string) $module, IModuleDescriptor::class, true)){
/** @var IModuleDescriptor $module */
return $module::models();
}else return [];
}, WFWModulesCollector::modules()
)
);
\ No newline at end of file
<?php
return [
];
\ No newline at end of file
return [];
\ No newline at end of file
<?php
return [
];
\ No newline at end of file
return [];
\ No newline at end of file
......@@ -4,15 +4,15 @@ namespace wfw\site\core;
use wfw\engine\core\app\WebApp;
/**
* Point d'entrée
* Entry point
*/
final class Main {
/**
* Constructeur. Appelé par le fichier index.php recevant la requête
* Called by the index.php file that recieve the request.
* @param array $args Arguments
*/
public function __construct(array $args=[]){
$contextInfos = (require dirname(__DIR__)."/config/site.context.php")($args);
new WebApp(new $contextInfos["class"](...$contextInfos["args"]));
new WebApp(new $contextInfos["class"](...($contextInfos["args"] ?? [])));
}
}
\ No newline at end of file
......@@ -102,7 +102,7 @@ try{
$confs->getKVSContainer($name),
$confs->getKVSDefaultStorage($name) ?? null
),
array_unique(require $confs->getModelsToLoadPath($name))
require $confs->getModelsToLoadPath($name)
),
$confs->getUsers($name),
$confs->getGroups($name),
......
......@@ -279,7 +279,8 @@ final class WriterWorker extends Worker {
new RequestError($e)
));
$this->_environment->getLogger()->log(
"$tags Report successfully sent to client",
"$tags Report successfully sent to client. Error summary : "
.$e->getMessage()." in ".$e->getFile()." at ".$e->getLine(),
ILogger::LOG
);
}catch(\Exception | \Error $e){
......
......@@ -53,7 +53,7 @@ final class ModelManager implements IModelManager, ICrossModelAccess {
*/
public function dispatch(EventList $eventList): void {
foreach($eventList->toArray() as $e){
$this->_domainEventManager->dispatch($e);
$this->_domainEventManager->dispatchDomainEvent($e);
}
}
......@@ -234,7 +234,7 @@ final class ModelManager implements IModelManager, ICrossModelAccess {
$this->_domainEventManager->removeEventListenerByClassName($class);
}
//On Cherche les models à gérer
foreach($this->_loader->getModelList() as $modelName){
foreach($this->_loader->getModelList() as $modelName=>$modelParams){
/** @var IEventListenerModel $model */
$model = $this->_loader->load($modelName);
if(is_null($model)) throw new \InvalidArgumentException(
......
<?php
return [
\wfw\engine\package\users\data\model\UserModel::class
\wfw\engine\package\users\data\model\UserModel::class => []
];
\ No newline at end of file
......@@ -22,8 +22,9 @@ use wfw\engine\core\command\inflectors\NamespaceBasedInflector;
use wfw\engine\core\command\security\CommandAccessRuleFactory;
use wfw\engine\core\command\security\CommandSecurityCenter;
use wfw\engine\core\command\security\ICommandSecurityCenter;
use wfw\engine\core\command\security\rules\CommandAccessAccessRulesCollector;
use wfw\engine\core\command\security\rules\CommandAccessRulesCollector;
use wfw\engine\core\command\SynchroneCommandBus;
use wfw\engine\core\conf\WFWModulesCollector;
use wfw\engine\core\data\DBAccess\NOSQLDB\msServer\IMSServerAccess;
use wfw\engine\core\data\DBAccess\NOSQLDB\msServer\MSServerWriterAccess;
use wfw\engine\core\data\DBAccess\SQLDB\IDBAccess;
......@@ -159,7 +160,7 @@ class DefaultContext implements IWebAppContext {
?string $errorViewPath = null,
?string $ajaxViewPath = null,
array $connections = [],
array $langs = [],
?array $langs = [],
array $securityRules = [],
array $hooks = [],
array $diceRules = [],
......@@ -173,6 +174,23 @@ class DefaultContext implements IWebAppContext {
$genericFactory = new DiceBasedFactory($this->_dice = $dice = new Dice());
$this->getErrorHandler()->handle();
$this->_dice->addRules([
ICacheSystem::class => [
'instanceOf' => APCUBasedCache::class,
'shared' => true ,
'constructParams'=>[ $projectName ]
]
]);
$modules = $this->getCacheSystem()->get(self::CACHE_KEYS[self::MODULES]);
if(is_null($modules)){
WFWModulesCollector::collectModules();
$this->getCacheSystem()->set(
self::CACHE_KEYS[self::MODULES],
WFWModulesCollector::modules()
);
}
//TODO : init if not set for commands and queries
$commandRules = $securityRules["command"] ?? [];
$queryRules = $securityRules["query"] ?? [];
......@@ -182,13 +200,11 @@ class DefaultContext implements IWebAppContext {
if(count($commandRules) === 0) $commandRules = WFWDefaultSecurityPolicy::commandsPolicy();
if(count($queryRules) === 0) $queryRules = WFWDefaultSecurityPolicy::queriesPolicy();
$this->_dice->addRules([
ICacheSystem::class => [
'instanceOf' => APCUBasedCache::class,
'shared' => true ,
'constructParams'=>[$projectName]
]
]);
$lc = $this->getCacheSystem()->get(self::CACHE_KEYS[self::LANGS]);
if(is_null($lc)){
$langs = WFWModulesCollector::langs($langs);
$this->getCacheSystem()->set(self::CACHE_KEYS[self::LANGS],$langs);
}else $langs = $lc;
$this->_conf = $conf = $this->initConfs($confFiles ?? [
dirname(__DIR__,3)."/config/conf.json",
......@@ -235,7 +251,7 @@ class DefaultContext implements IWebAppContext {
$this->_translator = $translator = $this->initTranslator($langs,null);
$instance = $this;
$commandRulesCollector = new CommandAccessAccessRulesCollector(
$commandRulesCollector = new CommandAccessRulesCollector(
new CommandAccessRuleFactory($genericFactory),$commandRules
);
......@@ -293,7 +309,8 @@ class DefaultContext implements IWebAppContext {
],
ICommandBus::class => [ 'instanceOf' => SynchroneCommandBus::class, 'shared' => true ],
ICommandInflector::class => [
'instanceOf' => NamespaceBasedInflector::class, 'shared' => true
'instanceOf' => NamespaceBasedInflector::class, 'shared' => true,
'constructParams' => [ $this->getCommandHandlers() ]
],
ICommandSecurityCenter::class => [
'instanceOf' => CommandSecurityCenter::class, 'shared'=>true,
......@@ -538,6 +555,38 @@ class DefaultContext implements IWebAppContext {
return $listeners;
}
/**
* @return array
*/
protected function getCommandHandlers():array{
$handlers = $this->getCacheSystem()->get(self::CACHE_KEYS[self::COMMAND_HANDLERS]);
$site = dirname(__DIR__,4).'/site';
$engine = dirname(__DIR__,3);
if(is_null($handlers)){
if(file_exists("$site/config/load/command.handlers.php"))
$handlers = require("$site/config/load/command.handlers.php");
else $handlers = require("$engine/config/default.command.handlers.php");
$this->getCacheSystem()->set(self::CACHE_KEYS[self::DOMAIN_EVENT_LISTENERS],$handlers);
}
return $handlers;
}
protected function getAccessRules():array{
//TODO : implements
}
protected function getCommandRules():array{
//TODO : implements
}
protected function getQueryRules():array{
//TODO : implements
}
protected function getHooks():array{
//TODO : implements
}
/**
* @return IErrorHandler Gestionnaire d'erreurs.
*/
......
......@@ -23,15 +23,21 @@ interface IWebAppContext {
public const CONF="CONF";
public const TRANSLATOR = "TRANSLATOR";
public const DOMAIN_EVENT_LISTENERS = "DOMAIN_EVENT_LISTENERS";
public const COMMAND_HANDLERS = "COMMAND_HANDLERS";
public const ROUTER="ROUTER";
public const VIEWS="VIEWS";
public const LANGS="LANGS";
public const MODULES="MODULES";
public const CACHE_KEYS = [
self::CONF => "WFW/WebApp/Confs",
self::ROUTER => "WFW/WebApp/Router",
self::TRANSLATOR => "WFW/WebApp/Translator",
self::DOMAIN_EVENT_LISTENERS => "WFW/WebApp/DomainEventListeners",
self::VIEWS => "WFW/WebApp/Views"
self::VIEWS => "WFW/WebApp/Views",
self::LANGS => "WFW/WebApp/Langs",
self::COMMAND_HANDLERS => "WFW/WebApp/CommandHandlers",
self::MODULES => "WFW/WebApp/Modules"
];
/**
......
......@@ -17,15 +17,35 @@ final class NamespaceBasedInflector implements ICommandInflector {
private $_factory;
/** @var ICommandHandler[][] $_handlers */
private $_handlers;
/** @var ICommandHandler $_resolved */
private $_resolved;
/**
* NamespaceBasedInflector constructor.
*
* @param ICommandHandlerFactory $factory Factory de handlers
* @param array $handlers
*/
public function __construct(ICommandHandlerFactory $factory) {
public function __construct(ICommandHandlerFactory $factory, array $handlers = []) {
$this->_factory = $factory;
$this->_handlers = [];
$this->_resolved = [];
foreach ($handlers as $commandClass => $handlerClasses){
if(!is_a($commandClass,ICommand::class,true))
throw new \InvalidArgumentException(
"$commandClass doesn't implements ".ICommand::class
);
if(!isset($this->_handlers[$commandClass])) $this->_handlers[$commandClass] = [];
foreach($handlerClasses as $class=>$params){
if(!is_a($class,ICommandHandler::class,true))
throw new \InvalidArgumentException(
"$class doesn't implements ".ICommandHandler::class
);
$this->_handlers[$commandClass][$class] = $factory->buildCommandHandler(
$class,$params
);
}
}
}
/**
......@@ -51,20 +71,29 @@ final class NamespaceBasedInflector implements ICommandInflector {
* @return array
*/
private function resolveHandlersFromCommandClass(string $command): array {
if(isset($this->_handlers[$command])){
return $this->_handlers[$command];
$res = [];
if(isset($this->_resolved[$command])){
$res[] = $this->_handlers[$command];
}else{
$res = [];
$r = [];
$tmp = explode("\\",$command);
$className = array_pop($tmp);
try{
$res[] = $this->_factory->buildCommandHandler(
$r[] = $this->_factory->buildCommandHandler(
implode('\\',$tmp).'\\handlers\\'.$className.'Handler'
);
}catch(\Exception $e){}
return $res;
$this->_handlers[$command] = array_merge(
$this->_handlers[$command] ?? [],
$r
);
$res[] = $r;
}
foreach($this->_handlers as $class=>$handlers){
if(is_a($command,$class)) $res[] = $handlers;
}
return array_merge(...$res);
}
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ use wfw\engine\core\command\security\ICommandAccessRuleFactory;
/**
* Try to collect all rules that must be applyed to commands. Rules are collected in constructor.
*/
final class CommandAccessAccessRulesCollector implements ICommandAccessRulesCollector {
final class CommandAccessRulesCollector implements ICommandAccessRulesCollector {
/** @var OrCommandAccessRule $_rule */
private $_rule;
......
......@@ -23,7 +23,8 @@ interface IModuleDescriptor {
public static function confs():array;
/**
* @return string[] List of translation files.
* @return string[] List of translation files indexed by lang
* ["en"=>[files], "fr"=>[files]]
*/
public static function langs():array;
......@@ -32,6 +33,16 @@ interface IModuleDescriptor {
*/
public static function models():array;
/**
* @return array List of classes that implements IDomainEventListener => [constructParams]
*/
public static function domainEventListeners():array;
/**
* @return array List of classes that implements ICommandHandlers => [constructParams]
*/
public static function commandHandlers():array;
/**
* @return string[] List of class that implements ISecurityPolicy and that must be applyed.
*/
......
......@@ -6,7 +6,7 @@ namespace wfw\engine\core\conf;
* Base module descriptor. All module descript may extends this in order to prevent incompatibilities
* if new methods are added to the interface.
*/
abstract class ModuleDescriptor implements IModuleDescriptor{
abstract class ModuleDescriptor implements IModuleDescriptor{
/**
* @return array Dependency injection rules to add to the general DI
......@@ -52,4 +52,18 @@ namespace wfw\engine\core\conf;
$reflected = new \ReflectionClass(static::class);
return dirname($reflected->getFileName());
}
/**
* @return string[] List of classes that implements IDomainEventListener
*/
public static function domainEventListeners(): array {
return [];
}
/**
* @return string[] List of classes that implements ICommandHandlers
*/
public static function commandHandlers(): array {
return [];
}
}
\ No newline at end of file
......@@ -9,12 +9,61 @@ use wfw\engine\core\security\WFWDefaultSecurityPolicy;
*/
final class WFWModulesCollector extends ModuleDescriptor implements IAppModulesCollector {
private static $_modules = [];
private static $_collected = false;
/**
* @param array $langs
* @return array
*/
public static function langs(?array $langs=null): array {
$langs = is_array($langs) ? array_flip($langs) : null;
$engineFiles = array_diff(
scandir(dirname(__DIR__,2)."/config/lang"),
['.','..']
);
$siteFiles = [];
if(is_dir(dirname(__DIR__,3).'/site/config/lang')){
$siteFiles = array_diff(
scandir(dirname(__DIR__,3)."/site/config/lang"),
['.','..']
);
}
$indexedFiles = [];
array_filter(
array_merge(
array_map(function($path){
return dirname(__DIR__,2)."/config/lang/$path";
},$engineFiles),
array_map(function($path){
return dirname(__DIR__,3)."/site/config/lang/$path";
},$siteFiles)
),
function($path) use ($langs,&$indexedFiles):bool{
$index = explode(".",basename($path))[0];
if(is_null($langs) || isset($langs[$index])){
if(!isset($indexedFiles[$index])) $indexedFiles[$index] = [];
$indexedFiles[$index][] = $path;
return true;
}
return false;
});
return $indexedFiles;
}
/**
* @return array
*/
public static function securityPolicies(): array {
return [ WFWDefaultSecurityPolicy::class ];
return array_merge(
[ WFWDefaultSecurityPolicy::class ],
...array_map(
function($module){
/** @var IModuleDescriptor $module */
return $module::securityPolicies();
},
self::$_modules
)
);
}
/**
......@@ -31,13 +80,16 @@ final class WFWModulesCollector extends ModuleDescriptor implements IAppModulesC
}
/**
* @param string $fileName Name of register files for modules
* @param string $fileName Name of register files for modules
* modules.
*/
public static function collectModules(string $fileName = "module.registration.php"): void {
$dir = dirname(__DIR__,3);
$out = [];
exec("find \"$dir\" -f -name \"$fileName\"",$out);
foreach($out as $file) require_once $file;
if(!self::$_collected){
$dir = dirname(__DIR__,3);
$out = [];
exec("find \"$dir\" -f -name \"$fileName\"",$out);
foreach($out as $file) require_once $file;
}
}
/**
......
......@@ -15,9 +15,13 @@ class GenericModelBuilder implements IModelBuilder {
/**
* @param string $model
*
* @param array $params
* @return IModel
*/
public function buildModel(string $model): IModel {
return new $model(new ArithmeticSearcher(new ArithmeticSolver(new ArithmeticParser())));
public function buildModel(string $model, array $params = []): IModel {
if(count($params) > 0) return new $model(...$params);
else return new $model(
new ArithmeticSearcher(new ArithmeticSolver(new ArithmeticParser()))
);
}
}
\ No newline at end of file
......@@ -15,7 +15,8 @@ interface IModelBuilder {
*
* @param string $model Classe du model à instancier
*
* @param array $params
* @return IModel
*/
public function buildModel(string $model):IModel;
public function buildModel(string $model, array $params = []):IModel;
}
\ No newline at end of file
......@@ -23,7 +23,7 @@ class KVStoreBasedModelLoader implements IModelLoader {
*/
public function __construct(IKVSAccess $access, array $allowedToLoad) {
$this->_kvs = $access;
foreach($allowedToLoad as $model){
foreach($allowedToLoad as $model=>$params){
if(!is_string($model) || !class_exists($model)
|| !is_a($model,InMemoryEventBasedModel::class,true)){
throw new \InvalidArgumentException("$model is not a compatible Model !");
......@@ -40,15 +40,10 @@ class KVStoreBasedModelLoader implements IModelLoader {
* @return IModel|null
*/
public function load(string $model): ?IModel {
if(!is_bool(array_search($model,$this->_allowedToLoad))){
if($this->_kvs->exists($model)){
return $this->_kvs->get($model);
}else{
return null;
}
}else{
throw new \InvalidArgumentException("$model is not a valide model name !");
}
if(isset($this->_allowedToLoad[$model])){
if($this->_kvs->exists($model)) return $this->_kvs->get($model);
else return null;
}else throw new \InvalidArgumentException("$model is not a valide model name !");
}
/**
......
......@@ -20,7 +20,7 @@ class ModelSnapshoter implements IModelSnapshoter {
private $_models;
/** @var array $_modelsClass */
private $_modelsClass;
/** @var array $_lastEventNumber */
/** @var int $_lastEventNumber */
private $_lastEventNumber;
/** @var string $_snapshotDir */
private $_snapshotDir;
......@@ -48,7 +48,7 @@ class ModelSnapshoter implements IModelSnapshoter {
$this->_serializer = $serializer;
$this->_modelBuilder = $builder;
$this->_db = $access;
$this->_modelsClass = (function(string... $models){return $models;})(...$models);
$this->_modelsClass = $models;
$this->_models = [];
if(!is_dir($snapshotDirectory)){
throw new \InvalidArgumentException("$snapshotDirectory is not a valide directory ");
......@@ -138,12 +138,13 @@ class ModelSnapshoter implements IModelSnapshoter {
//Si de nouveaux models ont été ajoutés, on les mets à jour
$additionnalModels = [];
foreach($this->_modelsClass as $model){
foreach($this->_modelsClass as $model=>$params){
if(!class_exists($model) || !is_a($model,IModel::class,true)){
throw new \InvalidArgumentException("$model doesn't implements ".IModel::class);
}else{
$additionnalModels[$model] = $this->_modelBuilder->buildModel($model);
}
}else $additionnalModels[$model] = $this->_modelBuilder->buildModel(
$model,
$params
);
}
$this->updateModels($additionnalModels, 0, $this->_lastEventNumber );
......
......@@ -41,22 +41,23 @@ final class ConfBasedDomainEventObserver implements IDomainEventObserver {
foreach ($listeners as $eventClass => $listenerClasses){
if(is_a($eventClass,IDomainEvent::class,true)){
foreach($listenerClasses as $k=>$listenerClass){
foreach($listenerClasses as $listenerClass => $params){
if(!is_a($listenerClass,IDomainEventListener::class,true))
throw new \InvalidArgumentException(
"Invalid domain event listener class at offset $k : ".$listenerClass
"Invalid domain event listener class ".$listenerClass
." does'nt implements ".IDomainEventListener::class
);
}
$registeredListernerClasses = [];
foreach($observer->getDomainEventListeners($eventClass) as $listener){
$registeredListernerClasses[] = get_class($listener);
$registeredListernerClasses[get_class($listener)] = true;
}
if(!isset($this->_listeners[$eventClass])) $this->_listeners[$eventClass] = [];
foreach($listenerClasses as $class=>$params){
if(!isset($registeredListernerClasses[$class]))
$this->_listeners[$eventClass][$class] = $params;
}
$this->_listeners[$eventClass] = array_diff(
$listenerClasses,
$registeredListernerClasses
);
}else{
throw new \InvalidArgumentException(
"Invalid domain event class : $eventClass doesn't implements "
......@@ -93,10 +94,13 @@ final class ConfBasedDomainEventObserver implements IDomainEventObserver {
private function initListenersForEvent(IDomainEvent $e){
foreach($this->_listeners as $eventsClass=>$listeners){
if($e instanceof $eventsClass){
foreach($listeners as $listenerClass){
foreach($listeners as $listenerClass=>$params){
$this->addDomainEventListener(
$eventsClass,
$this->_listenerFactory->buildDomainEventListener($listenerClass)
$this->_listenerFactory->buildDomainEventListener(
$listenerClass,
$params
)
);
}
unset($this->_listeners[$eventsClass]);
......@@ -120,7 +124,7 @@ final class ConfBasedDomainEventObserver implements IDomainEventObserver {
* @param IDomainEventListener $listener Listener à appeler
*/
public function addDomainEventListener(string $domainEventClass, IDomainEventListener $listener) {
$this->_observer->addDomainEventListener($domainEventClass, $listener);
$this->_observer->addDomainEventListener($domainEventClass,$listener);
}
/**
......@@ -136,6 +140,9 @@ final class ConfBasedDomainEventObserver implements IDomainEventObserver {
string $domainEventClass,
?IDomainEventListener $listener = null
) {
$this->_observer->removeDomainEventListener($domainEventClass, $listener);
$this->_observer->removeDomainEventListener(
$domainEventClass,
$listener
);
}
}
\ No newline at end of file
......@@ -22,9 +22,9 @@ final class Translator implements ITranslator {
* @throws \InvalidArgumentException
*/
public function __construct(ILanguageLoader $loader, array $langs, ?string $defaultLang=null) {
if(empty($langs)){
throw new \InvalidArgumentException("At least one lang have to be specified !");
}
if(empty($langs)) throw new \InvalidArgumentException(
"At least one lang must be specified !"
);
$this->_repositories = [];
foreach ($langs as $lang=>$paths){
$paths = array_filter($paths,function($path){
......@@ -78,9 +78,15 @@ final class Translator implements ITranslator {
* @param string $lang Nouvelle langue par défaut.
*/
public function changeCurrentLanguage(string $lang): void {
if(isset($this->_repositories[$lang])){
$this->_defaultLang = $lang;
}else{
if(isset($this->_repositories[$lang])) $this->_defaultLang = $lang;
else{
//try to resolve case were weak language form is given (ex fr for fr_FR)