<?php
namespace App\Package\Admin\Main\Controller\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\{ Request, Response };
use App\Package\Toolkit\Exception\Exception\Exception,
App\Package\Toolkit\Exception\NotFoundException\NotFoundException;
use App\Package\Admin\Main\EntityInterface\{ AdminActionInterface, AdminModuleInterface };
use App\Package\Admin\Tools\AdminState\AdminState;
/**
* RouteController
*
* Main routing point for administration panel (urls: panel.{domain})
*
* IMPORTANT: All controllers to which this controller fowards to MUST BE REGISTERED AS SERVICES
*
* @todo if not exact amount of needed arguments were passed then throw exception
*
* @author Daniel Balowski <d.balowski@openform.pl> (_creator)
* @copyright Openform
* @since 03.2019
*/
class RouteController extends AbstractController
{
/**
* @var AdminState
*/
protected $adminState;
/**
* Configuration of controller paths
* - see yml parameter "app.admin.controller.paths"
*
* @var string
*/
protected $controllerPaths;
/**
* @var string
*/
protected $targetControllerPath;
/**
* @param AdminState $adminState
* @param array $controllerPaths
*/
public function __construct(AdminState $adminState, array $controllerPaths)
{
$this->adminState = $adminState;
$this->controllerPaths = $controllerPaths;
}
/**
* Main routing point
*
* @param Request $request
* @param string|null $moduleSlug (optional)
* @param string|null $objectId (optional)
* @param string|null $actionSlug (optional)
* @param string|null $additional (optional)
*
* @return void
*/
public function indexAction(
Request $request,
string $moduleSlug = null,
string $objectId = null,
string $actionSlug = null,
string $additional = null
) : Response {
$locale = $request->getLocale();
// 1. Get current module and verify if is enabled
$currentModule = $this->adminState->getCurrentModule();
$this->verifyAdminModule($currentModule, $moduleSlug);
// 2. Get current action and verify if is enabled
$currentAction = $this->adminState->getCurrentAction();
$this->verifyAdminAction($currentAction, $actionSlug);
// 3. Verify if current action is registered within target controller
$this->setTargetControllerPath($currentModule)
->verifyControllerRegisteredActions($currentModule, $currentAction);
// 4. Verify if request method (GET, POST, ..) is allowed for current action
$this->verifyActionRequestMethods($currentModule, $currentAction, $request);
// 5. Verify if all required arguments for current action are set
$passedArgs = ( new \ReflectionMethod(get_class($this), 'indexAction') )->getParameters();
$passedValues = func_get_args();
$availableArgs = [];
foreach($passedArgs as $key => $passedArg) {
$availableArgs[ $passedArg->getName() ] = $passedValues[ $key ];
}
$argsToBePassed = $argsToBeLogged = $this->verifyActionRequiredArguments($currentModule, $currentAction, $availableArgs);
// 6. Log to request target point and args (for profiler)
foreach($argsToBeLogged as $key => $value) {
if ( is_object($value) && (get_class($value) == Request::class) ) {
$argsToBeLogged[ $key ] = 'Request object (see Request tab for more info)';
}
}
$request->_targetPoint =
[
'controller' => $this->targetControllerPath,
'action' => $currentAction->getName() . 'Action',
'args' => $argsToBeLogged
];
// 7. Invoke target action and forwards its return value
return call_user_func_array(
[ $this->get( $this->targetControllerPath ), $currentAction->getName() . 'Action' ],
$argsToBePassed
);
}
/**
* Verifies current module
* - checks if NOT NULL
* - checks if ENABLED
*
* @param AdminModuleInterface|null $adminModule (optional)
* @param string|null $moduleSlug (optional)
*
* @throws Exception a) if module does not exist
* b) if module is not enabled
*
* @return void
*/
protected function verifyAdminModule(AdminModuleInterface $adminModule = null, string $moduleSlug = null) : void
{
if (! $adminModule) {
throw new NotFoundException(
' >> 404 AdminModule -' . ($moduleSlug ? ' slug: ' . $moduleSlug : ' name: Home')
);
}
if (! $adminModule->getStat()) {
throw new NotFoundException(
' >> AdminModule is disabled - slug: ' . ($moduleSlug ? ' slug: ' . $moduleSlug : ' name: Home')
);
}
return;
}
/**
* Verifies current action
* - checks if NOT NULL
* - checks if ENABLED
*
* @param AdminActionInterface|null $adminAction (optional)
* @param string|null $actionSlug (optional)
*
* @throws NotFoundException a) if action does not exist
* b) if action is not enabled
*
* @return void
*/
protected function verifyAdminAction(AdminActionInterface $adminAction = null, string $actionSlug = null) : void
{
if (! $adminAction) {
throw new NotFoundException(
' >> 404 AdminAction - slug: ' . ($actionSlug ? ' slug: ' . $actionSlug : ' name: index')
);
}
if (! $adminAction->getStat()) {
throw new NotFoundException(
' >> AdminAction is disabled - slug: ' . ($actionSlug ? ' slug: ' . $actionSlug : ' name: index')
);
}
return;
}
/**
* Sets target controller path
*
* @param AdminModuleInterface $adminModule
*
* @return RouteController
*/
protected function setTargetControllerPath(AdminModuleInterface $adminModule) : RouteController
{
if ( array_key_exists(
$singleControllerHash = $adminModule->getPackage() . '.' . $adminModule->getName(),
$this->controllerPaths[ 'single' ] )
) {
$this->targetControllerPath = $this->controllerPaths[ 'single' ][ $singleControllerHash ];
return $this;
}
if ( array_key_exists($adminModule->getPackage(), $this->controllerPaths[ 'package' ]) ) {
$targetControllerPath = null;
foreach($this->controllerPaths[ 'package' ][ $adminModule->getPackage() ] as $packagePath) {
$targetControllerPath =
$this->has($packagePath . '\\' . $adminModule->getName() . 'Controller') ?
$packagePath . '\\' . $adminModule->getName() . 'Controller' :
$targetControllerPath;
}
$this->targetControllerPath = $targetControllerPath;
}
$this->targetControllerPath =
$this->targetControllerPath ??
'App\\Package\\' . $adminModule->getPackage() . '\\Controller\\'
. $adminModule->getName() . 'Controller';
return $this;
}
/**
* Gets target controller path
*
* @return string
*/
public function getTargetControllerPath() : string
{
return $this->targetControllerPath;
}
/**
* Verifies if current action is registered within target controller
*
* @param AdminModuleInterface $adminModule
* @param AdminActionInterface $adminAction
*
* @throws Exception if action not registered to target controller is tried to be accessed
*
* @return void
*/
protected function verifyControllerRegisteredActions(
AdminModuleInterface $adminModule,
AdminActionInterface $adminAction
) : void {
$registeredActions = $this->get( $this->targetControllerPath )->getConfiguration()->getRegisteredActions();
if (! in_array($adminAction->getName(), $registeredActions)) {
throw new Exception(
' >> You have tried to access action not registered to target controller' . PHP_EOL
. ' [ Controller: ' . ucfirst($adminModule->getName()) . ' ]' . PHP_EOL
. ' [ Action: ' . $adminAction->getName() . ' ]' . PHP_EOL
. ' [ Registered actions: ' . implode(', ', $registeredActions) . ' ]'
);
}
return;
}
/**
* Verifies if request method (GET, POST, ..) is allowed for current action
*
* @param AdminModuleInterface $adminModule
* @param AdminActionInterface $adminAction
* @param Request $request
*
* @throws Exception if request method is not allowed for current action
*
* @return void
*/
protected function verifyActionRequestMethods(
AdminModuleInterface $adminModule,
AdminActionInterface $adminAction,
Request $request
) : void {
if (! $adminAction->getMethods()) {
return;
}
$methods = explode(',', str_replace(' ', '', $adminAction->getMethods()));
foreach($methods as $method) {
if (strtolower($request->getMethod()) === strtolower($method)) {
return;
}
}
throw new Exception(
' >> Request method not allowed for current action - method: ' . $request->getMethod() . PHP_EOL
. ' [ Controller: ' . $adminModule->getName() . ' ]' . PHP_EOL
. ' [ Action: ' . $adminAction->getName() . ' ] ' . PHP_EOL
. ' [ Allowed methods: ' . $adminAction->getMethods() . ' ]'
);
}
/**
* Verifies if all required arguments for current action are set
*
* @param AdminModuleInterface $adminModule
* @param AdminActionInterface $adminAction
* @param array $availableArgs
*
* @throws Exception if there is missing argument for current action
*
* @return array
*/
protected function verifyActionRequiredArguments(
AdminModuleInterface $adminModule,
AdminActionInterface $adminAction,
array $availableArgs
) : array {
$requiredArgs = ( new \ReflectionMethod($this->targetControllerPath, $adminAction->getName() . 'Action') )->getParameters();
$argsToBePassed = [];
foreach($requiredArgs as $requiredArg) {
if (
! array_key_exists($requiredArg->getName(), $availableArgs) ||
(! $requiredArg->isOptional() && ! $availableArgs[ $requiredArg->getName() ])
) {
throw new Exception(
' >> Missing argument/argument value for current action - name: $' . $requiredArg->getName() . PHP_EOL
. ' [ Controller: ' . $adminModule->getName() . ' ]' . PHP_EOL
. ' [ Action: ' . $adminAction->getName() . ' ] ' . PHP_EOL
. ' [ Required arguments: ' . implode(', ', array_map( function($a) { return '$' . $a->getName(); }, $requiredArgs )) . ' ]'
);
}
$argsToBePassed[] = $availableArgs[ $requiredArg->getName() ];
}
return $argsToBePassed;
}
}