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...
Related
Per https://documentation.concrete5.org/developers/routing/routing-basics one captures parameters from the route as follows:
$router->get('/api/customer/{customerId}', function($customerId) {
return 'The customer ID is: ' . $customerId;
});
However, per https://documentation.concrete5.org/developers/routing/controllers one shouldn't use closure and instead use a separate controller as follows:
$router->get('/api/current_user', 'Application\Api\Controller\UserController::getCurrentUser');
class UserController
{
public function getCurrentUser()
{
//...
}
}
How do I pass a parameter when using a controller? I would have expected the following, but $customerId is not passed to the controller method.
$router->get('/api/customer/{customerId}', 'Application\Api\Controller\UserController::getSomeUser');
class UserController
{
public function getSomeUser($customerId)
{
//...
}
}
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([]);
}
}
In my Laravel app I have the following controller that takes the instance of Elastic search as a first parameter and then another variable:
use Elasticsearch\Client;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class AngularController extends Controller {
public function selectRandom(Client $es, $numRows) {
# ...search params here
$results = $es->search($searchParams);
return $results;
}
}
I need to call the method of the controller above from another controller, I do it like this:
class HomeCtrl extends Controller {
public function index() {
$featured = new AngularController();
return $featured->selectRandom(12);
}
}
I get the following error
Argument 1 passed to
App\Http\Controllers\AngularController::selectRandom() must be an
instance of Elasticsearch\Client, integer given
I'm not well versed in OOP. Do I call it incorrectly? Because I though the method I call would take instance that is injected in controller, not from where I call it.
When you are calling a method from your Controller to another
Controller that means you are doing wrong. For this purpose you should
use service.
Create a class in app\Services Utility.php
use Elasticsearch\Client;
class Utility {
public function selectRandom(Client $es, $numRows) {
# ...search params here
$results = $es->search($searchParams);
return $results;
}
}
and just inject this class to your controller
In your AngularController class selectRandom method first parameter is a class instance second one is number,that's why you get this error.If you want to access this method using object form another controller you need to set first parameter of this object and second one is your id.Another solution is here
some modify in selectRandom method
public function selectRandom($numRows) {
# ...search params here
$es = new Client();
$results = $es->search($searchParams);
return $results;
}
Then you use this function
public function index() {
$featured = new AngularController();
return $featured->selectRandom(12);
}
I want to access to my TestControler in DefaultController. So I've create a new instance, but the container is null. If I want to call a method, symfony throws a FatalErrorException:
Error: Call to a member function get() on a non-object in
DefaultController:
/**
* DefaultController.
*
*/
class DefaultControllerextends Controller
{
public function indexAction()
{
$contrTest = new TestController();
var_dump($contrTest);
}
var_dump result:
object(test\testBundle\Controller\TestController)#283 (1) {
["container":protected]=> NULL }
How can i do that?
Using other controllers inside a controller is a sign of bad architecture. Usually, it means you have to split the controller into a service, which you can use everywhere, and a controller.
For instance, when you have a controller which has a parseAction which parses a file and you need to use that in another controller too, you must create a acme_demo.parser.the_file_type service (give it which name you want) and use that in both controllers:
// ...
class FirstController extends Controller
{
public function xxxAction()
{
$parser = $this->get('acme_demo.parser.the_file_type');
$data = $parser->parse(...);
}
}
// ...
class SecondController extends Controller
{
public function yyyAction()
{
$parser = $this->get('acme_demo.parser.the_file_type');
$data = $parser->parse(...);
}
}
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