in my ZF2 (2.4.5) project I have main (parent) controller with function to validate user rights so every inherited controller can easily acces it. However there's a problem with redirect. I know that inherited controller's action has to return response, but is it possible to force redirection in parent controller?
Parent controller
<?php
namespace Application;
use Zend\Mvc\Controller\AbstractActionController;
class CoreController extends AbstractActionController{
public function checkAccess($moduleName, $accessName){
if($this->getAclService()->isAllowed($moduleName, $accessName)){
self::redirect()->toRoute('access-denied');
}
}
}
Inherited controller
namespace Application\Controller;
use Application\CoreController;
use Zend\View\Model\ViewModel;
class InterfaceController extends CoreController{
public function indexAction(){
$this->checkAccess('Foo', 'Bar');
return new ViewModel([
]);
}
}
TL;DR If I call $this->checkAccess('Foo', 'Bar'); in InterfaceController and $this->getAclService()->isAllowed($moduleName, $accessName) in CoreController returns false I want to redirect user to route 'access-denied' immediately without completing rest of InterfaceController::indexAction
Important: I want to avoid checking what checkAccess returns, I just force redirection.
Thanks in advance for response.
Ok I did this using global exception handler
Child controller
<?php
namespace Warehouse\Controller;
use Application\CoreController;
use Zend\View\Model\ViewModel;
class IndexController extends CoreController {
public function getWarehouseDocumentAction() {
parent::checkAccess('Warehouse', 'incoming-goods');
return new ViewModel([
'foo' => 'bar',
]);
}
}
Parent controller
namespace Application;
use Application\Exception\InsufficientPermissionException;
use Zend\Mvc\Controller\AbstractActionController;
class CoreController extends AbstractActionController {
public function checkAccess($moduleName, $accessName){
if(!$this->getServiceLocator()->get(MyAcl::class)->isAllowed($moduleName, $accessName, $this->identity())){
throw new InsufficientPermissionException('Access denied. Insufficient permission.');
}
}
}
Module.php
<?php
namespace Application;
use Application\Exception\InsufficientPermissionException;
use Application\Monolog\Handler\DoctrineLogMessageHandler;
use Zend\Mvc\MvcEvent;
class Module {
public function onBootstrap(MvcEvent $e) {
$sharedEvents = $e->getApplication()->getEventManager()->getSharedManager();
$sharedEvents->attach('Zend\Mvc\Application', 'dispatch.error', function (MvcEvent $event) {
if (php_sapi_name() !== 'cli') {
$exception = $event->getParam('exception');
if ($exception instanceof InsufficientPermissionException) {
$target = $event->getTarget();
return $target->redirect()->toRoute('access-denied');
}
}
});
}
}
Permissions are held in database.
You are doing a simple 302 http redirect to "access-denied", so you could just render the response object thus far and stop php execution:
public function checkAccess($moduleName, $accessName){
if (!$this->getAclService()->isAllowed($moduleName, $accessName))) {
self::redirect()->toRoute('access-denied');
$this->getResponse()->send();
exit;
} else {
return true;
}
}
or you could simple throw an exception:
public function checkAccess($moduleName, $accessName){
if (!$this->getAclService()->isAllowed($moduleName, $accessName))) {
self::redirect()->toRoute('access-denied');
throw new \Exception('access denied');
} else {
return true;
}
}
the exception will prevent further code execution and the redirect will prevent the exception error page to be rendered.
I'm going out on a limb here by saying that is not possible. The calling code, InterfaceController::indexAction, needs to at least return which will start the redirect process.
You can clean it up a bit by letting the base controller set the redirect but the inherited controller needs to stop the script execution by calling return.
Base Controller
use Zend\Mvc\Controller\AbstractActionController;
class CoreController extends AbstractActionController{
public function checkAccess($moduleName, $accessName){
if (!$this->getAclService()->isAllowed($moduleName, $accessName))) {
self::redirect()->toRoute('access-denied');
return false;
} else {
return true;
}
}
}
Inherited controller
use Application\CoreController;
use Zend\View\Model\ViewModel;
class InterfaceController extends CoreController{
public function indexAction(){
if (!$this->checkAccess('Foo', 'Bar')) {
return;
}
return new ViewModel([
]);
}
}
EDIT
As a side note, in our company we do our ACL checks in the base controller's init() method with is not overridden and therefore can do the redirect instantly before any action code is run.
EDIT #2
I totally forgot about the init method which we use. Give this a go if you're looking for another solution.
Base controller
use Zend\Mvc\Controller\AbstractActionController;
class CoreController extends AbstractActionController{
public function init() {
// bootstrap code...
// This should redirect
if (!$this->_isUserAuthorized()) {
return;
}
// If the ACL check was OK then pass control to controllers
return parent::init();
}
private function _isUserAuthorized() {
// checks done here
// return true if OK
// else
$this->_response->setRedirect($this->view->defaultUrl($redirect))->sendResponse();
return false;
}
}
Inherited controller
use Application\CoreController;
use Zend\View\Model\ViewModel;
class InterfaceController extends CoreController{
public function indexAction(){
// nothing to do here
return new ViewModel([]);
}
}
Related
In my laravel (7.x) application, I have a method called _index in different controllers & models with exactly same functionality (fetch the data to display in the grid) and parameters (except 1, that requires an additional parameter called available).
So, I created a super method in the base controller, something line this:
Controller.php
namespace App\Http\Controllers;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
protected function _index($ModelClass, $status, $available = null)
{
# models
$Model = new $ModelClass();
# accessing and returning method data
return $Model->_index(status: $status, available: $available);
}
}
SomeController.php
class SomeController extends Controller
{
public function _index()
{
# accessing super methods
$SomeModel = $this->_index(SomeModel::class, true);
...
# rendering
return view('some-view', compact($SomeModel));
}
}
class SomeModel extends Model
{
public function _index($status = null, $available = null) : array
{
if($available == true)
{
...
}
}
}
AnotherController.php
class AnotherController extends Controller
{
public function _index()
{
# accessing super methods
$AnotherModel = $this->_index(AnotherModel::class);
...
# rendering
return view('another-view', compact($AnotherModel));
}
}
class AnotherModel extends Model
{
public function _index($status = null) : array
{
...
}
}
Only SomeController / index is working fine but other controllers which does not required the $available parameter are showing Unknown named parameter $available.
Is there a way to ignore the missing parameters, as there is no point in including the parameter in the rest of the methods, throughout the application..?
I am not sure if this is the right way to handle this or not.
Controller.php
namespace App\Http\Controllers;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
protected function _index($ModelClass, $status, $available = null)
{
# models
$Model = new $ModelClass();
try
{
# accessing and returning method data
return $Model->_index(status: $status, available: $available);
}
catch (\Throwable $th)
{
# handling missing parameter error/exception
if($th->getMessage() == 'Unknown named parameter $available')
{
return $Model->_index(status: $status);
}
}
}
}
However, in case anybody finds a better way to handle this issue. Then do post your answer.
Thanks...
i have this error in my code and I have writing the "use" but i have this error:
Attempted to call an undefined method named "redirect" of class
"App\Controller\SetlocaleController".
My code:
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\RedirectResponse;
class SetlocaleController extends HomeController {
public function __construct(\Twig\Environment $twig)
{
$this->twig = $twig;
}
public function setLocaleAction(Request $request, $language = null)
{
if($language != null)
{
$session->set('_locale', $language);
}
$url = $request->headers->get('referer');
if(empty($url))
{
return new response($this->twig->render('page/home.html.twig'));
}
else{
return $this->redirect($url);
}
}
}
You have an answer for me please ?
Best practice solution
As proposed in the best practices for controllers documentation by Symfony, make sure you extend the Abstract controller on your controller.
// HomeController.php
// ...
class HomeController extends AbstractController {
// ...
No extra changes required to SetlocaleController. However if RedirectResponse is no longer used you can remove it's import
use Symfony\Component\HttpFoundation\RedirectResponse;
Solution using HttpFoundation\RedirectResponse
You need to use the already imported RedirectResponse object. Do not use the code: return $this->redirect($url); since, as the error states, there is no redirect(url) function defined for your class.
return new RedirectResponse($url);
I am using laravel 5.2,
I have created admin controller and added logic to check admin role in constructor
namespace App\Http\Controllers;
use Sentinel;
class AdminController extends Controller
{
public function __construct()
{
if(Sentinel::check())
{
if(!Sentinel::inRole('admin'))
{
return redirect("login");
}
}
else
{
return redirect("login");
}
}
}
and I extends this controller on some admin controller
namespace App\Http\Controllers;
use Request;
use App\Http\Controllers\AdminController;
use App\Http\Requests;
use Sentinel;
use App\User;
use DB;
class UserController extends AdminController
{
function __construct()
{
parent::__construct();
}
}
When I call user controller admin constructor is called but return function is not working properly, if I add die; before return it get die, but after return notthing is affected.
so it doesn't return redirect function properly.
The ugly workaround would be to pass a boolean param to Papa indicating that you do not wish to parse the code contained in it's constructor. i.e:
// main class that everything inherits
class Grandpa extends Controller
{
public function __construct()
{
}
}
class Papa extends Grandpa
{
public function __construct($bypass = false)
{
// only perform actions inside if not bypassing
if (!$bypass) {
}
// call Grandpa's constructor
parent::__construct();
}
}
class Kiddo extends Papa
{
public function __construct()
{
$bypassPapa = true;
parent::__construct($bypassPapa);
}
}
I am new into Phalcon framework. I just got the basic idea about it. Every controller has methods with multiple specific actions. I wrote a huge indexAction method but now I want to break it down with multiple private method so that I can reuse those functionality. But when I try to create any method without action suffix, it returns error(Page Not Found). How can I break it down into multiple methods?
<?php
use Phalcon\Mvc\Controller;
class PostsController extends Controller
{
public function indexAction()
{
$this->someMethod();
}
public function someMethod()
{
//do your things
}
}
Controllers must have the suffix “Controller” while actions the suffix “Action”. A sample of a controller is as follows:
<?php
use Phalcon\Mvc\Controller;
class PostsController extends Controller
{
public function indexAction()
{
}
public function showAction($year, $postTitle)
{
}
}
For calling another method, you would use it straight forward
<?php
use Phalcon\Mvc\Controller;
class PostsController extends Controller
{
public function indexAction()
{
echo $this->showAction();
}
private function showAction()
{
return "show";
}
}
Docs.
What exactly do you want? The answer seems trivial to me.
class YourController extends Phalcon\Mvc\Controller
{
// this method can be called externally because it has the "Action" suffix
public function indexAction()
{
$this->customStuff('value');
$this->more();
}
// this method is only used inside this controller
private function customStuff($parameter)
{
}
private function more()
{
}
}
I have this route: Route::controller('/', 'PearsController'); Is it possible in Laravel to get the PearsController to load a method from another controller so the URL doesn't change?
For example:
// route:
Route::controller('/', 'PearsController');
// controllers
class PearsController extends BaseController {
public function getAbc() {
// How do I load ApplesController#getSomething so I can split up
// my methods without changing the url? (retains domain.com/abc)
}
}
class ApplesController extends BaseController {
public function getSomething() {
echo 'It works!'
}
}
You can use (L3 only)
Controller::call('ApplesController#getSomething');
In L4 you can use
$request = Request::create('/apples', 'GET', array());
return Route::dispatch($request)->getContent();
In this case, you have to define a route for ApplesController, something like this
Route::get('/apples', 'ApplesController#getSomething'); // in routes.php
In the array() you can pass arguments if required.
( by neto in Call a controller in Laravel 4 )
Use IoC...
App::make($controller)->{$action}();
Eg:
App::make('HomeController')->getIndex();
and you may also give params
App::make('HomeController')->getIndex($params);
You should not. In MVC, controllers should not 'talk' to each other, if they have to share 'data' they should do it using a model, wich is the type of class responsible for data sharing in your app. Look:
// route:
Route::controller('/', 'PearsController');
// controllers
class PearsController extends BaseController {
public function getAbc()
{
$something = new MySomethingModel;
$this->commonFunction();
echo $something->getSomething();
}
}
class ApplesController extends BaseController {
public function showSomething()
{
$something = new MySomethingModel;
$this->commonFunction();
echo $something->getSomething();
}
}
class MySomethingModel {
public function getSomething()
{
return 'It works!';
}
}
EDIT
What you can do instead is to use BaseController to create common functions to be shared by all your controllers. Take a look at commonFunction in BaseController and how it's used in the two controllers.
abstract class BaseController extends Controller {
public function commonFunction()
{
// will do common things
}
}
class PearsController extends BaseController {
public function getAbc()
{
return $this->commonFunction();
}
}
class ApplesController extends BaseController {
public function showSomething()
{
return $this->commonFunction();
}
}
if you were in AbcdController and trying to access method public function test() which exists in OtherController you could just do:
$getTests = (new OtherController)->test();
This should work in L5.1