Commit f6ba38a5 authored by Jordan Breton's avatar Jordan Breton

WIP modules

parent 3e350c00
......@@ -45,9 +45,8 @@ final class NotFoundHook implements IActionHook {
if(preg_match("#$rule#",$action->getInternalPath()))
return new ErrorResponse(
"404",
$this->_translator->getTranslateAndReplace(
$this->_translator->getAndReplace(
$this->_translationKey,
null,
$action->getInternalPath()
)
);
......
......@@ -76,7 +76,6 @@ use wfw\engine\core\security\data\sanitizer\HTMLPurifierBasedSanitizer;
use wfw\engine\core\security\data\sanitizer\IHTMLSanitizer;
use wfw\engine\core\security\IAccessControlCenter;
use wfw\engine\core\security\IAccessRuleFactory;
use wfw\engine\core\security\WFWDefaultSecurityPolicy;
use wfw\engine\core\session\handlers\PHPSessionHandler;
use wfw\engine\core\session\ISession;
use wfw\engine\core\session\Session;
......@@ -151,7 +150,6 @@ class DefaultContext implements IWebAppContext {
* @param array $hooks Liste des hooks
* @param array $diceRules Regles à ajouter à Dice
* @param array $globals Contient la variables globales de php aux index _GET,_POST,_FILES,_SERVER
* @param array $confFiles Liste des fichiers de configuration à charger
* @param null|string $projectName Nom du projet. Sert de namespace pour les clés du cache.
* @throws \InvalidArgumentException
*/
......@@ -162,10 +160,9 @@ class DefaultContext implements IWebAppContext {
array $connections = [],
?array $langs = [],
array $securityRules = [],
array $hooks = [],
?array $hooks = null,
array $diceRules = [],
array $globals = [],
?array $confFiles = null,
?string $projectName = null
){
//pass installed packages to layout/action/response resolver
......@@ -182,34 +179,14 @@ class DefaultContext implements IWebAppContext {
]
]);
$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"] ?? [];
$accessRules = $securityRules["access"] ?? [];
if(count($accessRules) === 0) $accessRules = WFWDefaultSecurityPolicy::accessPolicy();
if(count($hooks) === 0) $hooks = WFWDefaultSecurityPolicy::hooksPolicy();
if(count($commandRules) === 0) $commandRules = WFWDefaultSecurityPolicy::commandsPolicy();
if(count($queryRules) === 0) $queryRules = WFWDefaultSecurityPolicy::queriesPolicy();
$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->loadModules();
$commandRules = $this->getCommandRules($securityRules["command"] ?? null);
$queryRules = $this->getQueryRules($securityRules["query"] ?? null);
$accessRules = $this->getAccessRules($securityRules["access"] ?? null);
$hooks =$this->getHooks($hooks);
$langs = $this->getLangs($langs);
$this->_conf = $conf = $this->initConfs($confFiles ?? [
dirname(__DIR__,3)."/config/conf.json",
dirname(__DIR__,4)."/site/config/conf.json"
]);
$this->_conf = $conf = $this->initConfs($this->getConfs());
//Trying to dynamicaly resolve the msserver socket addr. If MSServerPool is unavailable,
//we maked the assumption that the sockets are in their default locations.
......@@ -422,6 +399,7 @@ class DefaultContext implements IWebAppContext {
]
]);
$this->_translator->changeCurrentLanguage($action->getLang());
$this->_dice->addRules($this->getDi());
$this->_dice->addRules($diceRules);
}
......@@ -539,17 +517,31 @@ class DefaultContext implements IWebAppContext {
}
}
protected function loadModules():void{
$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()
);
}else WFWModulesCollector::restoreModulesFromCache($modules);
}
/**
* @return array
*/
protected function getDi():array{
return WFWModulesCollector::di();
}
/**
* @return array
*/
protected function getDomainEventListeners():array{
$listeners = $this->getCacheSystem()->get(self::CACHE_KEYS[self::DOMAIN_EVENT_LISTENERS]);
$site = dirname(__DIR__,4).'/site';
$engine = dirname(__DIR__,3);
if(is_null($listeners)){
if(file_exists("$site/config/load/domain_events.listeners.php"))
$listeners = require("$site/config/load/domain_events.listeners.php");
else $listeners = require("$engine/config/default.domain_events.listeners.php");
$listeners = WFWModulesCollector::domainEventListeners();
$this->getCacheSystem()->set(self::CACHE_KEYS[self::DOMAIN_EVENT_LISTENERS],$listeners);
}
return $listeners;
......@@ -560,34 +552,89 @@ class DefaultContext implements IWebAppContext {
*/
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");
$handlers = WFWModulesCollector::commandHandlers();
$this->getCacheSystem()->set(self::CACHE_KEYS[self::DOMAIN_EVENT_LISTENERS],$handlers);
}
return $handlers;
}
protected function getAccessRules():array{
//TODO : implements
/**
* @return array Confs files
*/
protected function getConfs():array{
$confFiles = $this->getCacheSystem()->get(self::CACHE_KEYS[self::CONF_FILES]);
if(is_null($confFiles)){
$confFiles = WFWModulesCollector::confs();
$this->getCacheSystem()->set(self::CACHE_KEYS[self::CONF_FILES],$confFiles);
}
return $confFiles;
}
protected function getCommandRules():array{
//TODO : implements
/**
* @param array|null $access
* @return array
*/
protected function getAccessRules(?array $access=null):array{
$rules = $this->getCacheSystem()->get(self::CACHE_KEYS[self::ACCESS_RULES]);
if(is_null($rules)){
$rules = WFWModulesCollector::accessPolicy(!empty($access) ? $access : null);
$this->getCacheSystem()->set(self::CACHE_KEYS[self::ACCESS_RULES],$rules);
}
return $rules;
}
protected function getQueryRules():array{
//TODO : implements
/**
* @param array|null $commands
* @return array
*/
protected function getCommandRules(?array $commands=null):array{
$rules = $this->getCacheSystem()->get(self::CACHE_KEYS[self::COMMAND_RULES]);
if(is_null($rules)){
$rules = WFWModulesCollector::commandsPolicy(!empty($commands) ? $commands : null);
$this->getCacheSystem()->set(self::CACHE_KEYS[self::COMMAND_RULES],$rules);
}
return $rules;
}
protected function getHooks():array{
//TODO : implements
/**
* @param array|null $queries
* @return array
*/
protected function getQueryRules(?array $queries=null):array{
$rules = $this->getCacheSystem()->get(self::CACHE_KEYS[self::QUERY_RULES]);
if(is_null($rules)){
$rules = WFWModulesCollector::queriesPolicy(!empty($queries) ? $queries : null);
$this->getCacheSystem()->set(self::CACHE_KEYS[self::QUERY_RULES],$rules);
}
return $rules;
}
/**
* @param array|null $hooks
* @return array
*/
protected function getHooks(?array $hooks=null):array{
$hooksPolicy = $this->getCacheSystem()->get(self::CACHE_KEYS[self::HOOKS]);
if(is_null($hooksPolicy)){
$hooksPolicy = WFWModulesCollector::hooksPolicy(!empty($hooks) ? $hooks : null);
$this->getCacheSystem()->set(self::CACHE_KEYS[self::HOOKS],$hooksPolicy);
}
return $hooksPolicy;
}
/**
* @param array $langs
* @return array
*/
protected function getLangs(array $langs):array{
$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;
return $langs;
}
/**
* @return IErrorHandler Gestionnaire d'erreurs.
......
......@@ -21,6 +21,7 @@ use wfw\engine\core\view\ILayoutResolver;
*/
interface IWebAppContext {
public const CONF="CONF";
public const CONF_FILES="CONF_FILES";
public const TRANSLATOR = "TRANSLATOR";
public const DOMAIN_EVENT_LISTENERS = "DOMAIN_EVENT_LISTENERS";
public const COMMAND_HANDLERS = "COMMAND_HANDLERS";
......@@ -28,6 +29,10 @@ interface IWebAppContext {
public const VIEWS="VIEWS";
public const LANGS="LANGS";
public const MODULES="MODULES";
public const ACCESS_RULES="ACCESS_RULES";
public const COMMAND_RULES="COMMAND_RULES";
public const QUERY_RULES="QUERY_RULES";
public const HOOKS = "HOOKS";
public const CACHE_KEYS = [
self::CONF => "WFW/WebApp/Confs",
......@@ -37,7 +42,12 @@ interface IWebAppContext {
self::VIEWS => "WFW/WebApp/Views",
self::LANGS => "WFW/WebApp/Langs",
self::COMMAND_HANDLERS => "WFW/WebApp/CommandHandlers",
self::MODULES => "WFW/WebApp/Modules"
self::MODULES => "WFW/WebApp/Modules",
self::ACCESS_RULES => "WFW/WebApp/AccessRules",
self::COMMAND_RULES => "WFW/WebApp/CommandRules",
self::QUERY_RULES => "WFW/WebApp/QueryRules",
self::HOOKS => "WFW/WebApp/Hooks",
self::CONF_FILES => "WFW/WebApp/ConfFiles"
];
/**
......
......@@ -2,12 +2,13 @@
namespace wfw\engine\core\conf;
use wfw\engine\core\security\ISecurityPolicy;
use wfw\engine\core\security\WFWDefaultSecurityPolicy;
/**
* Base descriptor
*/
final class WFWModulesCollector extends ModuleDescriptor implements IAppModulesCollector {
final class WFWModulesCollector extends ModuleDescriptor implements IAppModulesCollector, ISecurityPolicy {
private static $_modules = [];
private static $_collected = false;
......@@ -77,13 +78,7 @@ final class WFWModulesCollector extends ModuleDescriptor implements IAppModulesC
public static function securityPolicies(): array {
return array_merge(
[ WFWDefaultSecurityPolicy::class ],
...array_map(
function($module){
/** @var IModuleDescriptor $module */
return $module::securityPolicies();
},
self::$_modules
)
self::getSecurityPolicies()
);
}
......@@ -91,10 +86,12 @@ final class WFWModulesCollector extends ModuleDescriptor implements IAppModulesC
* @return array
*/
public static function di(): array {
return array_merge_recursive(...array_map(function($module){
$di = array_map(function($module){
/** @var IModuleDescriptor $module */
return $module::di();
},self::$_modules));
},self::$_modules);
if(count($di) > 1) return array_merge(...$di);
else return $di;
}
/**
......@@ -108,15 +105,15 @@ final class WFWModulesCollector extends ModuleDescriptor implements IAppModulesC
$engine = [];
if(file_exists($siteFile)) $site = require $siteFile;
if(file_exists($engineFile)) $engine = require $engineFile;
return array_merge_recursive(
array_merge_recursive(
$engine,
...array_map(function($module){
/** @var IModuleDescriptor $module */
return $module::domainEventListeners();
},self::$_modules)),
$site
);
return self::mergeConstructors(...array_merge(
array_merge(
[$engine],
array_map(function($module){
/** @var IModuleDescriptor $module */
return $module::commandHandlers();
},self::$_modules)),
[$site]
));
}
/**
......@@ -130,15 +127,18 @@ final class WFWModulesCollector extends ModuleDescriptor implements IAppModulesC
$engine = [];
if(file_exists($siteFile)) $site = require $siteFile;
if(file_exists($engineFile)) $engine = require $engineFile;
return array_merge_recursive(
array_merge_recursive(
$engine,
...array_map(function($module){
/** @var IModuleDescriptor $module */
return $module::domainEventListeners();
},self::$_modules)),
$site
);
return self::mergeConstructors(...array_merge(
array_merge(
[$engine],
array_map(function($module){
/** @var IModuleDescriptor $module */
return $module::domainEventListeners();
},
self::$_modules
)
),
[$site]
));
}
/**
......@@ -152,15 +152,18 @@ final class WFWModulesCollector extends ModuleDescriptor implements IAppModulesC
$engine = [];
if(file_exists($siteFile)) $site = require $siteFile;
if(file_exists($engineFile)) $engine = require $engineFile;
return array_merge(
return self::mergeConstructors(...array_merge(
array_merge(
$engine,
...array_map(function($module){
/** @var IModuleDescriptor $module */
return $module::models();
},self::$_modules)),
$site
);
[$engine],
array_map(function($module){
/** @var IModuleDescriptor $module */
return $module::models();
},
self::$_modules
)
),
[$site]
));
}
/**
......@@ -206,4 +209,106 @@ final class WFWModulesCollector extends ModuleDescriptor implements IAppModulesC
self::$_modules = $modules;
self::$_collected = true;
}
/**
* @param array ...$init
* @return array
*/
private static function mergeConstructors(array ...$init):array{
$res = [];
foreach($init as $initArray){
foreach($initArray as $key => $constructor){
if(!isset($res[$key])) $res[$key] = $constructor;
else{
foreach($constructor as $index => $param){
if(isset($res[$key][$index]) && is_array($res[$key][$index]) && is_array($param)){
$res[$key][$index] = array_merge_recursive(
$res[$key][$index],
$param
);
}else $res[$key][$index] = $param;
}
}
}
}
return $res;
}
/**
* @param array|null $accessRules Access rules to replace the default security policy
* @return array [AccessRuleClass => params]
*/
public static function accessPolicy(?array $accessRules=null): array {
return self::mergeConstructors(
$accessRules ?? WFWDefaultSecurityPolicy::accessPolicy(),
...array_map(
function($securityPolicy){
/** @var ISecurityPolicy $securityPolicy */
return $securityPolicy::accessPolicy();
}, self::getSecurityPolicies())
);
}
/**
* @return array
*/
private static function getSecurityPolicies():array{
$policies = array_map(function($module){
/** @var IModuleDescriptor $module */
return $module::securityPolicies();
},self::$_modules);
if(count($policies) > 1) return array_unique(array_merge(...$policies));
else return [];
}
/**
* @param array|null $commands Command rules to replace the default security policy
* @return array [CommandAccessRuleClass => params]
*/
public static function commandsPolicy(?array $commands = null): array {
return self::mergeConstructors(
$commands ?? WFWDefaultSecurityPolicy::commandsPolicy(),
...array_map(
function($securityPolicy){
/** @var ISecurityPolicy $securityPolicy */
return $securityPolicy::commandsPolicy();
},
self::getSecurityPolicies()
)
);
}
/**
* @param array|null $queries Queries rules to replace the default security policy
* @return array [QueryAccessRuleClass => params]
*/
public static function queriesPolicy(?array $queries = null): array {
return self::mergeConstructors(
$queries ?? WFWDefaultSecurityPolicy::queriesPolicy(),
...array_map(
function($securityPolicy){
/** @var ISecurityPolicy $securityPolicy */
return $securityPolicy::queriesPolicy();
},
self::getSecurityPolicies()
)
);
}
/**
* @param array|null $hooks
* @return array [HookClass => params ]
*/
public static function hooksPolicy(?array $hooks = null): array {
return self::mergeConstructors(
$hooks ?? WFWDefaultSecurityPolicy::hooksPolicy(),
...array_map(
function($securityPolicy){
/** @var ISecurityPolicy $securityPolicy */
return $securityPolicy::hooksPolicy();
},
self::getSecurityPolicies()
)
);
}
}
\ No newline at end of file
......@@ -33,7 +33,7 @@ final class WFWDefaultSecurityPolicy extends SecurityPolicy {
]
): array {
$base = [];
if($includeBase) $base = array_merge_recursive(
if($includeBase) $base = array_merge(
ContactAccessControlPolicies::accessPolicy(),
MielAccessControlPolicies::accessPolicy(),
NewsAccessControlPolicies::accessPolicy(),
......@@ -42,7 +42,7 @@ final class WFWDefaultSecurityPolicy extends SecurityPolicy {
GeneralAccessControlPolicies::accessPolicy(),
[ ValidToken::class => [] ]
);
return array_merge_recursive($templateArray,$base,$policies);
return array_merge($templateArray,$base,$policies);
}
/**
......@@ -59,7 +59,7 @@ final class WFWDefaultSecurityPolicy extends SecurityPolicy {
]
): array {
$base = [];
if($includeBase) $base = array_merge_recursive(
if($includeBase) $base = array_merge(
UsersAccessControlPolicies::hooksPolicy(),
GeneralAccessControlPolicies::hooksPolicy()
);
......
......@@ -36,8 +36,6 @@ final class RequireAuthentification implements IAccessRule {
private $_redirUrl;
/** @var null|IMessage $_message */
private $_message;
/** @var bool $_treeBased */
private $_treeBased;
/**
* RequireAuthentification constructor.
......@@ -48,7 +46,6 @@ final class RequireAuthentification implements IAccessRule {
* @param string[] ...$pathsToProtect Chemins à protéger.
* @param null|string $redirUrl
* @param null|string $translationKey
* @param bool $treeBased (optionnal) MUST BE TRUE if you want a tree based rule set !
*/
public function __construct(
ISession $session,
......@@ -56,14 +53,8 @@ final class RequireAuthentification implements IAccessRule {
ITranslator $translator,
array $pathsToProtect = [],
?string $redirUrl = null,
?string $translationKey = null,
bool $treeBased = false
?string $translationKey = null
){
$this->_treeBased = $treeBased;
if(!$this->_treeBased) $pathsToProtect = (function(string ...$paths){
return $paths;
})(...$pathsToProtect);
$this->_paths = $pathsToProtect;
$this->_notifier = $notifier;
$this->_session = $session;
......@@ -83,8 +74,7 @@ final class RequireAuthentification implements IAccessRule {
* vérifications.
*/
public function check(IAction $action): ?IAccessPermission {
if($this->_treeBased) return $this->treeCheck($action);
else return $this->linearCheck($action);
return $this->treeCheck($action);
}
/**
......@@ -104,14 +94,14 @@ final class RequireAuthentification implements IAccessRule {
foreach($internalPath as $pathPart){
$continue = false;
foreach($array as $k=>$path){
if(is_int($k)){
if($path === lcfirst($pathPart)) return $this->denyPublicAccess($action);
}else if($k === lcfirst($pathPart)){
if($k === lcfirst($pathPart)){
if(is_array($path)){
$array = $array[$k];
$continue = true;
break;
} else if($path === lcfirst($pathPart)) return $this->denyPublicAccess($action);
}else if(is_int($k)){
if($path === lcfirst($pathPart)) return $this->denyPublicAccess($action);
}
}
if(!$continue) break;
......@@ -119,19 +109,6 @@ final class RequireAuthentification implements IAccessRule {
return null;
}
/**
* Linear check (non tree based regexp set)
* @param IAction $action
* @return null|AccessPermission
*/
private function linearCheck(IAction $action): ?AccessPermission{
foreach($this->_paths as $path){
if(preg_match("#".$path."#",$action->getInternalPath()))
return $this->denyPublicAccess($action);
}
return null;
}
/**
* If user is not logged, then deny access.
* @param IAction $action
......
......@@ -6,6 +6,7 @@ use wfw\engine\core\command\ICommandBus;
use wfw\engine\core\domain\events\IDomainEvent;
use wfw\engine\core\domain\events\IDomainEventListener;
use wfw\engine\core\domain\events\IDomainEventObserver;
use wfw\engine\core\lang\ITranslator;
use wfw\engine\core\response\IResponse;
use wfw\engine\core\response\responses\Response;
use wfw\engine\core\session\ISession;
......@@ -22,23 +23,26 @@ final class ArchiveHandler extends DefaultContactActionHandler implements IDomai
private $_encoder;
/** @var array $_ids */
private $_ids;
/**
* ArchiveHandler constructor.
*
* @param ICommandBus $bus Bus de commandes
* @param ISession $session Session
* @param ContactIdListRule $rule Régle de validation des données
* @param ICommandBus $bus Bus de commandes
* @param ITranslator $translator
* @param ISession $session Session
* @param ContactIdListRule $rule Régle de validation des données
* @param IDomainEventObserver $observer Observeur de DomainEventListeners
* @param IJSONEncoder $encoder Encodeur JSON
*/
public function __construct(
ICommandBus $bus,
ITranslator $translator,
ISession $session,
ContactIdListRule $rule,
IDomainEventObserver $observer,
IJSONEncoder $encoder
) {
parent::__construct($bus, $rule, $session);
parent::__construct($bus, $rule, $session, $translator);
$this->_ids = [];
$this->_encoder = $encoder;
$observer->addDomainEventListener(ArchivedEvent::class, $this);
......
......@@ -2,6 +2,7 @@
namespace wfw\engine\package\contact\handlers\action;
use wfw\engine\core\command\ICommandBus;
use wfw\engine\core\lang\ITranslator;
use wfw\engine\core\security\data\IRule;
use wfw\engine\core\session\ISession;
use wfw\engine\package\general\handlers\action\PostDataDefaultActionHandler;
......@@ -19,9 +20,10 @@ abstract class DefaultContactActionHandler extends PostDataDefaultActionHandler{
* @param ICommandBus $bus Bus de commandes
* @param IRule $rule Régle de validation des données
* @param ISession $session Session
* @param ITranslator $translator
*/
public function __construct(ICommandBus $bus, IRule $rule, ISession $session) {
parent::__construct($bus, $rule);
public function __construct(ICommandBus $bus, IRule $rule, ISession $session, ITranslator $translator) {
parent::__construct($bus, $translator,$rule);
$this->_session = $session;
}
}
\ No newline at end of file
......@@ -6,6 +6,7 @@ use wfw\engine\core\command\ICommandBus;
use wfw\engine\core\domain\events\IDomainEvent;
use wfw\engine\core\domain\events\IDomainEventListener;
use wfw\engine\core\domain\events\IDomainEventObserver;
use wfw\engine\core\lang\ITranslator;
use wfw\engine\core\response\IResponse;
use wfw\engine\core\response\responses\Response;
use wfw\engine\core\session\ISession;
......@@ -22,23 +23,26 @@ final class MarkAsReadHandler extends DefaultContactActionHandler implements IDo
private $_encoder;
/** @var array $_ids */
private $_ids;
/**
* ArchiveHandler constructor.
*
* @param ICommandBus $bus Bus de commandes
* @param ISession $session Session
* @param ContactIdListRule $rule Régle de validation des données
* @param ICommandBus $bus Bus de commandes
* @param ISession $session Session
* @param ContactIdListRule $rule Régle de validation des données
* @param IDomainEventObserver $observer Observeur de DomainEventListeners
* @param IJSONEncoder $encoder Encodeur JSON
* @param ITranslator $translator
*/
public function __construct(
ICommandBus $bus,
ISession $session,
ContactIdListRule $rule,
IDomainEventObserver $observer,
IJSONEncoder $encoder
IJSONEncoder $encoder,
ITranslator $translator
) {
parent::__construct($bus, $rule, $session);
parent::__construct($bus, $rule, $session, $translator);
$this->_ids = [];
$this->_encoder = $encoder;
$observer->addDomainEventListener(MarkedAsReadEvent::class, $this);
......
......@@ -6,6 +6,7 @@ use wfw\engine\core\command\ICommandBus;
use wfw\engine\core\domain\events\IDomainEvent;
use wfw\engine\core\domain\events\IDomainEventListener;
use wfw\engine\core\domain\events\IDomainEventObserver;
use wfw\engine\core\lang\ITranslator;
use wfw\engine\core\response\IResponse;
use wfw\engine\core\response\responses\Response;
use wfw\engine\core\session\ISession;
......@@ -22,23 +23,26 @@ final class MarkAsUnreadHandler extends DefaultContactActionHandler implements I
private $_encoder;
/** @var array $_ids */
private $_ids;