I have a service that is requested in the constructor of my base controller, to make sure it always runs. This service checks some things from the database, and might error. If it errors, I want to redirect the user to a page that displays this error. I cannot find any way to accomplish this, as redirections can only be returned from a controller's action. What I want is to be able to 'return' a redirection from outside an action. What do I need to do to be able to redirect from my service? Do I have to implement my service differently, or not at all? I do not want to call this method manually on every action.
Relevant code:
// Every other controller in my bundle extends this one
class Controller extends SymfonyController
{
public function setContainer(ContainerInterface $container = null)
{
parent::setContainer($container);
$this->containerInitialized();
}
protected function containerInitialized()
{
// Initialize my.service before running action of every page
try {
$this->get('my.service');
} catch (SomeException $ex) {
// I want redirection to happen here
}
}
}
EDIT: I know I can use the controller kernel event to make something run on every page, which is what I am going to use. This still does not solve the redirection situation though.
Services are not supposed to decide if a redirect should happen or not or how to deal with the client's request and response. A service is meant to just provide a service to the controller that can be used independent of how the controller was called.
If you have a service that can trigger a redirect to a specific site, how do you use that service in another controller, another site or even in a command-line interface where redirect do not exist? You cannot, and that is bad.
Error-Handling in services are meant to be done with custom exceptions. If a service comes to a point where it cannot continue normally, an exception is thrown that describes the error that happened, but not how the error should be handled. The decision of how to deal with the error/exception should be done by the controller or command depending on what makes sense in the context of the controller. If there are different types of errors that a service can cause, these different types should be distinguishable by the exception (or the data in the exception) on which the controller can make the decision to redirect somewhere (or not). A service or exception should never impose on the controller how it should handle the error, only provide information for the controller(s) or command(s) to decide by themself.
Related
I am using Laravel Framework 5.8.21. I am trying to bind an interface to a class based on Request params inside AppServiceProvider's register method like this.
public function register()
{
$this->app->bind('App\Contracts\SomeInterface', 'App\Logic\ClassName');
if(Request::has('notification_type') && Request::get('notification_type') == 'email') {
$this->app->bind('App\Contracts\SomeInterface', 'App\Logic\SomeotherClassName');
}
}
and later injecting the interface to Controllers __construct() method.
with tests, it always bound to ClassName. I tried to get accessed URL inside AppServiceProvider and while running unit tests, it always returning / with $this->app->request->getRequestUri(); and method as GET even though from the test I am posting to URL like this.
$this->post('/notification', [
'notification_type' => 'email',
])->assertJson(
'message-push-status' => true,
]);
While testing it with Postman, when I try to post http://localhost:8000/notification, it says, 419 | Page Expired.
This is possible. The register method of AppServiceProvider or any other service provider has access to request parameters.
This can be accessed as follows:
public function register()
{
$this->app->bind('SomeInterface', function($app) {
return new SomeClass(request()->some_parameter);
});
}
You will not be able to reliably use the current request information from within a service provider.
First, it is a general best practice to not depend on application logic within the register() method directly. You may cause a race condition where you have a dependency that hasn't been registered yet, or cause unnecessary overhead (e.g. establish a database connection even if you don't need any querying).
Second, Laravel's request lifecycle won't funnel the current request into the application until after all of the service provider registration and bootstrapping has been completed.
Without knowing exactly what business logic you're trying to accomplish, you have at least a couple options:
Use contextual binding to serve different implementations of the same interface depending on the requesting object (e.g. controller).
Use a factory or a similar facilitator-style object that you can inject in the controller, and can provide the proper dependency based on your preferred logic.
I am using cakephp 3.0 ,In my application I want forced user logout or clear session in case if any internal error occurs,How can I accomplish it. Can I make any changes in the error controller and will it able to catch the specific internal errors.
CakePHP uses the ErrorController to render HTTP responses for both unhandled exceptions and fatal errors. Keep in mind that it's also used for 404 not found exceptions.
You can logout the user in ErrorController in the beforeFilter event, and render the error message as normal. Afterwards their session will have ended.
I give you a warning. The ErrorController should be kept as simple as possible. Should not perform heavy logic. The reason is very simple. If you generate a fatal error inside ErrorController it creates an endless loop.
Most people configure their AuthComponent in their base AppController which you should never extend as the base class for ErrorController.
That means you have to configure the AuthComponent separately in your ErrorController and hope it never fails and you keep the configuration consistent with what you have in AppController.
If you go that route. Also wrap your code in a try/catch block to prevent exceptions from being thrown from inside the ErrorController.
For example;
public function beforeFilter(Event $event)
{
try {
$this->Auth->logout();
}catch(\Exception $ex) {
// prevent endless loop
}
}
Alternative perform a 302 redirect:
It's better to keep ErrorController as vanilla as possible and not load the AuthComponent inside it. I assume you have a controller already set up for logging users in and out. Add a new route for that controller called "forced_out", and then redirect the URL to that route when there is an uncaught exception. Inside the action for "forced_out" you can log the current user out.
Create a new error handler class like this.
class ForceOutErrorHandler extends ErrorHandler {
protected function _displayException($exception)
{
// restore the old handler
(new ErrorHandler(Configure::read('Error')))->register();
throw new RedirectException(Router::url([
'controller'=>'Users',
'action'=>'forced_out'
));
}
}
Now register that error class only when a user is signed in. You can do this in the beforeFilter of your AppController like this:
public function beforeFlter(Event $event) {
if($this->Auth->user()) {
(new ForceOutErrorHandler(Configure::read('Error')))->register()
}
}
Note: I'm not sure if CakePHP will catch the redirect exception from inside the error handler like that or not. You might have to use header(); die() as a last try.
I'm updating a PHP framework I've written. It used to just use a default behavior for routing. For example consider a case where the request goes to domain.com/package/controller/method...
$url = ["package", "controller", "method"];
//Check if package exists...
//Check if controller exists in package...
//Check if method exists in controller...
This is all well and good, and works perfectly. However, I wanted to add some additional functionality to my router. That functionality being the ability to define custom routes, and pass an anonymous function which does whatever you want.
However, supposing that the request does not match any of the user-defined routes, I want to use the default functionality I have now to check if there are additional possible routes. That way I can update old projects with the new framework and not have them break, and additionally...I just like this default behavior because most of the time routes are not that complicated and defining routes feels like a violation of DRY to me.
The problem is that I don't want to pass the user-defined routes as an array to the object constructor. Rather, I want the user to call them as methods on the base application object similar to how laravel or express handles this.
The problem is that I want the default route checking to happen AFTER the user's defined routes have been checked not before. This quasi-code might help you understand what I mean...
class App
{
__construct
{
//Check Default Routing
}
private function get()
{
//Get Request
}
private function post()
{
//Post Request
}
private function put()
{
//Put Request
}
private function delete()
{
//Delete Request
}
}
app::get();
In the above case, the default routing would take place before the user-defined routes are called. I looked at the PHP consrtuctor/destructor page and learned about __destruct. However, after reading this question I'm a little bit unsure this would work.
PHP.net says...
The destructor method will be called as soon as there are no other
references to a particular object, or in any order during the shutdown
sequence.
The first part of that explanation sounds like exactly what I want. I.E. as soon as all of the methods have been called on the application object, we'll run the __destruct function which will check if the user-defined routes were fruitful, and if not, check if the default routing system yields any results.
The problem is that I'm not sure if this is bad practice, or simply won't work. Can I require a file, set my controller, and then call a method on that controller from within __destruct? Are there limitations that would effect the code within these controllers? Supposing that there is a problem using __destruct this way, what are my alternatives, keeping in mind I don't like either of these solutions...
Having the user call the default routing as a method at the end of their script.
Passing routes in as arrays to the constructor.
I think you're confused here. Take note of this from the PHP Manual
The destructor method will be called as soon as there are no other references to a particular object, or in any order during the shutdown sequence.
To put this a different way, there's two reasons to call a destructor
The class is being garbage collected. In other words, you've overwritten or unset all the references to the class instance. This means you can't directly call this class anymore
The PHP script has reached its end and the thread is shutting down.
In other words, there's nothing left for the class to do. But in your own statement you say this
The first part of that explanation sounds like exactly what I want. I.E. as soon as all of the methods have been called on the application object, we'll run the __destruct function which will check if the user-defined routes were fruitful, and if not, check if the default routing system yields any results.
There is no "and" here to work with. That's the point. In fact, there's very few places you would use this.
What you need is to think in layers. So you'd have a controller layer that you'd call to check the methods. In turn, that controller opens a new layer that checks user functions. That class or method should return something or throw an Exception if it fails. On failure it can then try to use default methods. This is how you need to structure your program. Trying to use a destructor to do this would likely only confuse people. Make the data flow explicit, not implicit (which is what magic methods do).
I'm trying to figure out if its bad practice to initiate a controller from within a router class. From what little I have been able to find about this, some say that the router shouldn't handle instantiating controllers. Below is how I started to develop my router class.
Example(note I'm omitting alot for the sake of typing.)
class Router {
private $url, $controller;
public function __construct($url)
{
$this->url = $url;
$this->map(); /* maps url to controller and action*/
/*dispatch controller*/
$this->dispatch();
}
private function dispatch()
{
$controller = new $this->controller();
$controller->executeAction();
}
}
To answer you question I'd so no it violates separation of concerns. The router shouldn't be worried what controller handles it's request, or rather how that controller came into being. It only needs to know that at some point, even in the event of a 404 that some controller will handle it.
Now injecting a controller into the route would be ok, then you could prototype it as an interface like so,
public function dispatch(ControllerInterface $Controller){
.....
}
otherwise you have to much hard linking, to much dependency, what if you need a second controller?
For example say you need an admin controller and a public controller, and a members controller. Do you then build 3 routers.
Personally the approach I am planning for a project I am working on is to use an event driven system, where there will be a group of controllers assigned by default as in a traditional routing system ( class/method/args... ) , say a controller folder will be searched for them. Other wise a controller will register itself to listen for a particular http request. So the flow is a bit like this.
Are there any registered listeners for this request,
if no are there any controllers in our controller folder that match the routing schema,
and at the very end is a 404 controller that will handle any request.
If any of these catch it the event ( that's being listened to is terminated ). The advantage of this over a purely hard wired route is, say I want to make a payment plugin, which needs a payment page, how do I put that in the controller folder as a third party vender? This way one only needs activate the plugin, which registers for the "payments" route, and listens within it's own package.
Maybe this is not a concern of you project but it's something to think about.
I have a mobile site that I added detection to for iPhones and other iOS devices. The iOS page needs a different layout and views than the regular pages (which are actually for older mobile devices). So, I have some code that does mobile detection, that part was easy. What I'd like to do is make it so that Zend automagically finds and uses the correct layout and view when an iOS device is detected, but that has turned out to be surprisingly hard...
I needed it to be up and running ASAP, so I did a quick and dirty hack that worked: in each action function, I have a simple If statement that detects if the iOS boolean flag has been set (which happens in the controller's init), and if so, overrides the layout and view explicitly. Existing code (in the actions):
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone'); // 'osr' is the name of the app
$this->_helper->viewRenderer->setRender('iphone/index');
}
So this works, but it's kinda ugly and hacky and has to be put in each action, and each action's Renderer has to be set, etc. I got to reading about the Zend ContextSwitch, and that seemed like exactly the kind of thing I should use (I'm still kind of new to Zend), so I started messing around with it, but can't quite figure it out.
In the controller's init, I'm initializing the ContextSwitch, adding a context for 'iphone' and setting the suffix to 'iphone', and now what I'd like to do is have a single place where it detects if the user is an iOS device and sets the context to 'iphone', and that should make it automatically use the correct layout and view. New code (in the controller's init):
$this->_helper->contextSwitch()->initContext();
$contextSwitch = $this->_helper->getHelper('contextSwitch');
$contextSwitch->addContext('iphone', array('suffix' => 'iphone'));
$contextSwitch->setAutoDisableLayout(false);
if ($_SESSION['user']['iPhone']) {
//$this->_currentContext = 'iphone'; // Doesn't work.
//$contextSwitch->initContext('iphone'); // Doesn't work.
//$contextSwitch->setContext('iPhone'); // Not the function I'm looking for...
// What to put here, or am I barking up the wrong tree?
}
I did some reading on the contextSwitcher, and it seems like there is a lot of stuff on, e.g. setting it to be specific to each particular action (which I don't need; this needs to happen on every action in my app), and going through and modifying all the links to something like /osr/format/iphone to switch the context (which I also don't really need or want; it's already a mobile site, and I'd like the layout/view switch to be totally transparent to the user and handled only from the backend as it is with my quick and dirty hack). These seem like basically an equal amount of code to my quick and dirty hack. So... Anyone have some suggestions? I'm really hoping for just a single line like "$contextSwitch->setContext('iphone');" that I could use in an If statement in my controller's init, but the Zend documentation is awful, and I can't seem to find any examples of people doing something like this on Google or SO.
Ok I think I figured out how to put this into a plugin:
The Plugin:
//This is my own namespace for ZF 1.x library, use your own
class My_Controller_Plugin_Ios extends Zend_Controller_Plugin_Abstract {
public function preDispatch(Zend_Controller_Request_Abstract $request) {
parent::preDispatch($request);
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone');
$this->_helper->viewRenderer->setRender('iphone/index');
}
}
}
register the plugin in your application.ini
resources.frontController.plugins.ios = "My_Controller_Plugin_Ios"
I think that's all there is to it. Although you may want to look into the userAgent plugin
ContextSwitch operates off the "format" property in the request object (by default). You need to set it somewhere in your app
$requestObject->setParam('format', 'iphone').
I'd set it in a bootstrap, or more appropriately, a controller plugin, but where it goes really depends on your app.
I don't use Zend ContextSwitch so I can't really help there, but you could use some inheritance in your controllers to set all layouts in just a couple of lines. Even though it might still be classed as a "hack" it is a way better hack
Now whenever you execute a action Zend first fires a number of other functions within the framework first, such as the routing, the preDispatch, Action helpers and so on. It also fires a number of things after the action such as PostDispatch. This can be used to your advantage.
First create a controller called something like "mainController" and let it extend Zend_Controller_action and in this controller create a function called predispatch()
Second. Extend your normal controllers to mainController. Since we now have a function called predispatch() Zend will automatically fire this on every controller, and if you do your iPhone/iOS check there it will automagically be performed on every action on every controller, as long as you don't overwrite the method in your controller (you can make this method final to prevent this). You can offcourse use a multitude of different non-Zend functions and/or helpers within the mainctroller to make the code as compact and reusable as possible Se example code below:
<?php
/**
*Maincontroller
*/
class MainController extends Zend_Controller_Action
{
/**
* Predispatch function is called everytime an action is called
*/
final public function preDispatch(){
//for security reasons, make sure that no one access mainController directly
$this->request = $this->getRequest();
if (strtolower($this->request->controller)=='main')
$this->_redirect('/index/index/');
//Check for iPhone
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone'); // 'osr' is the name of the app
$this->_helper->viewRenderer->setRender('iphone/index');
}
}
}
<?php
/**
*Othercontroller
*/
class OtherController extends MainController
{
/**
* The correct layout for IndexAction is already set by the inherited preDispatch
*/
public function indexAction(){
/* YOUR CODE HERE */
}
}
For a good overview of the dispatch process check these links (same picture in both):
http://nethands.de/download/zenddispatch_en.pdf
http://img.docstoccdn.com/thumb/orig/22437345.png