I have two route groups in my Laravel application, one for an API and one for the website itself. I've added the following code in global.php for error handling of my API.
App::error(function(ModelNotFoundException $e)
{
return Response::json('', 404);
});
But not this obviously also has effect on my normal website, where I want to return a normal view when the ModelNotFoundException occurs. Like so:
App::error(function(ModelNotFoundException $e)
{
return Response::view('404.html', [], 404);
});
How can I setup different error handlers for different route groups?
I think you shouldn't care what part of the site that threw the error, but instead respond with whatever format the client requested. In general this is set in the Accepts header of the request and can be accessed in Laravel by:
if (Request::format() == 'json')
{
//
}
(above is taken from the documentation)
In your case, this would turn your error handling function to this:
App::error(function(ModelNotFoundException $e)
{
if (Request::format() == 'json') {
return Response::json('', 404);
} else {
return Response::view('404.html', [], 404);
}
});
This automatically covers you if, for instance, you add an non-API AJAX request to the main portion of your website (for whatever reason) that could potentially trigger a ModelNotFoundException. As long as your client sends the appropriate request headers, you're good.
You could try with changing environment. For selected group you could change environment using:
$app->setEnvironment('enviromentname');
and create new environmentname.php file in start directory
But I don't know if it will work.
You could also create a session variable and in App:error add code depending on this session variable:
$value = Session::get('type');
if ($value == 'onetype') {
// something
}
else {
// something else
}
Related
I am using Zend Expressive 2 due to PHP version constraints. If I return variables in step one of pipeline (IndexAction) the variables appear just fine.
If I delegate to the next step (VerifyInputAction) and determine there is an error in the input, I need to return an error to view script. For some reason, it will not take the variables with it that I pass with the template renderer. It will still load the template, just not with the $data array variables.
I'm using Zend View as the template renderer.
My pipeline looks as follows.
IndexAction()
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
if ($request->getMethod() !== "POST") {
return new HtmlResponse($this->template->render('app::home-page', ['error' => 'hello']));
} else {
$delegate->process($request);
//return new HtmlResponse($this->template->render('app::home-page'));
}
}
VerifyInputaction()
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
$data = [];
$file = $request->getUploadedFiles()['recordsFile'];
$fileType = substr($file->getClientFilename(), strpos($file->getClientFilename(), '.'));
// If file type does not match appropriate content-type or does not have .csv extension return error
if (! in_array($file->getClientMediaType(), $this->contentTypes) || ! in_array($fileType, $this->extensions)) {
$data['error']['fileType'] = 'Error: Please provide a valid file type.';
return new HtmlResponse($this->template->render('app::home-page', $data));
}
$delegate->process($request);
}
Another problem that might be beyond the scope of this question includes, when I make it to the next Action in the pipeline, if I go to render a view script there I get this error...
Last middleware executed did not return a response. Method: POST Path: /<--path-->/ .Handler: Zend\Expressive\Middleware\LazyLoadingMiddleware
I will do my best to provide more code examples, but due to this being an issue at work I might have some problems with that.
Thanks!
Last middleware executed did not return a response. Method: POST Path: /<--path-->/ .Handler: Zend\Expressive\Middleware\LazyLoadingMiddleware
An action needs to return a response. In your VerifyInputaction you don't return a response if there is no valid csv file. I'm guessing this happens in your case and the $delegate->process($request); is triggered, which probably doesn't call another action which returns a middleware.
Looking at your code, it makes more sense to call VerifyInputaction first, check if it is a post and verify. If any of those fails, go to the next action which would be IndexAction. This could display the form with an error message. You can pass error message within the request as explained here: https://docs.zendframework.com/zend-expressive/v2/cookbook/passing-data-between-middleware/
Pipeline:
VerifyInputaction -> Check POST, verify input -> redirect if success
IndexAction -> render template and return response
I don't see any reason in your code why $data is not passed. My guess is that somehow the template is rendered in IndexAction which doesn't have the $data but has error set. You might check for this. The confusion is here that you render the same template in 2 different actions. Using the solution I mentioned, you only need to render it in IndexAction.
I'm currently implementing a Services Oriented Architecture in Symfony2, and I would like to have our recommandations about it.
I want to know what I need to handle in the Controller, and what I need to handle in the service.
An extreme solution would be to pass the Request to the service, and it will implement all the logic (mostly Doctrine, as I'm developping an API and not a "full" site with Twig for example), and then send back a Responsein the controller.
That means I would have to create code like this :
In the service :
if (null === $entity) {
throw new \Exception('Not found.', self::NOT_FOUND);
}
And in the controller :
try {
$service->doThings();
}
catch (\Exception $e) {
if ($e->getCode() === Service::NOT_FOUND) {
return new Response($e->getMessage(), 404);
}
}
With potentially one condition for every exception I need to throw.
Is this a good way to go ? Am I completly wrong about the implementation ?
Everything that is related to controllers logic (such as, take a request, response to a request, make a form, bind parameters, extract an entity from db (better with ParamConverter), get and set session objects, redirects, and so on) should be kept into controllers.
Everything else could be migrated into services (or other classes)
I am working on one project where following things are expected,
To write a web service to get users list and individual users information
To write a web service to do CRUD operations.
I understand that GET method will be useful for fetching information and execution of that API call via browser but can any one help me for following items?,
To make API call if I want to use "PUT", or "POST" or "DELETE" methods? How to do it via browser? or what would be best way to do it?
I have to use Stored procedure to fetch the information from DB for GET method?
I suggest you to use a slim Php microframework, it provides a powerful, extensible framework for building a REST API.
So, The typical REST conventions for URL requests you'll use:
GET /items: Retrieve a list of items
GET /items/22: Retrieve item 22
POST /items: Create a new item
PUT /items/22: Update item 22
DELETE /items/22: Remove item 22
Slim's URL router will help you to make it easier.
And YES, you have to create your data object to fetch from server's side, one tipycall example to get it:
// handle GET requests for /articles/:id
$app->get('/items/:id', function ($id) use ($app) {
try {
// query database for single items
$items = R::findOne('items', 'id=?', array($id));
if ($items) {
// if found, return JSON response
$app->response()->header('Content-Type', 'application/json');
echo json_encode(R::exportAll($items));
} else {
// else throw exception
throw new ResourceNotFoundException();
}
} catch (ResourceNotFoundException $e) {
// return 404 server error
$app->response()->status(404);
} catch (Exception $e) {
$app->response()->status(400);
$app->response()->header('X-Status-Reason', $e->getMessage());
}
});
Finally, this tutorial may help you to a total work around
[http://www.androidhive.info/2014/01/how-to-create-rest-api-for-android-app-using-php-slim-and-mysql-day-12-2/][1]
I think this is more of a general question (so not php restricted) with regards to ddd and the command pattern.
Let's say I execute a CreatePostCommand from within the create action of my controller, the command will be handled and eventually executed successfully. What's the appropriate way to notify the controller which response to return in case the command did fail or succeed? Given the command handler will fire a domain specific event, I could hook up the controller to the event, but that seems a quite awkward, also not appropriate for every situation (e.g. a post could be created somewhere else and the controller really doesn't know about this :) ).
public function createAction($title, $content)
{
$this->commandBus->execute(new CreatePostCommand($title, $content);
$this->render('…'); // what if the command execution failed?
}
Any thoughts on this?
I think if you are really trying to follow the DDD command pattern then you need to treat the command bus as a fire and forget asynchronous process that may take a long time to complete.
Consider immediately redirecting to a command verifier controller. It's up to the command verifier to actively check the status of the command and see if it worked.
In most cases, the command will have finished successfully and your verifier can then redirect once again to continue normal flow.
If the command fails then the verifier puts up an appropriate error message.
If the command is in progress then you can entire a redirect loop while informing the user that the command is in progress.
Something like:
// Execute the command
$command = new CreatePostCommand($title, $content);
$this->commandBus->execute($command);
return redirect '/command-verifier/' . $command->getId();
// The verification action
public function verifyCommandAction($commandId)
$commandStatus = $this->commandBus->getStatus($commandId);
if ($commandStatus == SUCCESS) redirect to all is well;
if ($commandStatus == FAILED) then oops;
if ($commandStatus == IN_PROGRESS) then maybe pause a bit and redirect again while keeping the user informed.
Clearly there is quite a bit of hand waving going on but I think this is the most general approach especially with php where every request starts from ground zero.
The way I'm currently doing it is as follows (excuse long post).
public function createAction($title, $content) {
try {
$post = $this->commandBus->execute(new CreatePostCommand($title, $content);
}
catch (Exception $e) {
return $this->render('some error template file', $e);
}
return $this->render('successful creation template file', $post);
}
This way, you're creating a post and if everything goes as planned, return the $post object and send that to your view. On the other hand, when an exception is thrown during execution, you catch that error and send it to a view.
My preferred way is to have the controller call a method on a service that manages that behaviour, and have the controller injected as a listener that manages the responses, ie:
public function createAction($title, $content) {
$service = new CreateActionService($title, $content);
return $service->create($this);
}
public function onError(Exception $e) {
return $this->render('some error template file', $e);
}
public function onSuccess($post) {
return $this->render('success', $post);
}
Then in your service...
public function create($listener)
{
try {
$this->commandBus->execute(new CreatePostCommand($title, $content);
}
catch (Exception $e) {
return $this->listener->onError($e);
}
return $this->listener->onSuccess($post);
}
This way your service is managing the various results that the command handler may return, and your controller is left simply to manage the responses that you may wish returned to your presentation layer.
I try to set up a maintenance page with ZF2 but it's not working. I put a maintenance.html page in public folder (www) and in my onbootstrap function I've got the following code :
$config = $e->getApplication()->getServiceManager()->get('Appli\Config');
if($config['maintenance']) {
$response = $e->getResponse();
$response->getHeaders()->addHeaderLine('Location', '/maintenance.html');
$response->setStatusCode(503);
return $response;
}
I enter the if cause $config['maintenance'] is true but it's not displaying my maintenance.html page as expected. Instead it displays the page asked.
Is there something wrong about my redirection ?
You appear to be attempting to short-circuit the request directly from your onBootstrap method. That won't work, at that point the route hasn't been resolved and the controller hasn't been dispatched. Essentially, all you're doing is pre-populating the response, only for it to be over-written once the request is routed and dispatched.
If you want to affect the response, you'll need to listen to one of the other MvcEvents. It seems you want to do this before a controller is dispatched, so the place to do it would be in the EVENT_ROUTE, ideally with a high priority so it happens before the route is resolved by the router (saves wasted processing resolving a route that will never be dispatched).
public function onBootstrap(MvcEvent $e)
{
$events = $e->getApplication()->getEventManager();
$events->attach(MvcEvent::EVENT_ROUTE, function (MvcEvent $r) {
$config = $r->getApplication()->getServiceManager()->get('Appli\Config');
if ($config['maintenance']) {
$response = $r->getResponse();
// set content & status
$response->setStatusCode(503);
$response->setContent('<h1>Service Unavailable</h1>');
// short-circuit request...
return $response;
}
}, 1000);
}
You can't set a 503 status code and redirect - the two are mutually exclusive, as redirects use a 3xx status code.
You probably want something more like:
$response->setContent(file_get_contents('/path/to/maintenance.html'));
$response->setStatusCode(503);