I'm trying to clean up some of my controllers by moving the functionality from the controller methods to reusable commands that I can just pass on to a command bus. As I've understood, though, a command is not supposed to return data (or did I misunderstand something?). But how else would you suggest for the controller to act on the outcome of the command?
For instance I have a Create endpoint in my ClientController. The controller method dispatches a CreateClient command, and then I'd like to redirect the user to the new Client's edit page. Something like
class ClientController extends Controller
{
public function create($request)
{
try {
$this->dispatch(new CreateClient(/*get something from the request to pass*/));
} catch (\Exception $e) {
return $this->json(['error' => $e->getMessage()]);
}
$client = ...;
$this->redirect('/client/' . $client->id);
}
}
If the creation failed I can throw an exception, which can be handled by the controller. I could of course create a method in my ClientRepository to get the newest created Client, but that seems error prone. But how would you suggest that I got a hold of the newly created client?
Edit:
At the end of https://www.youtube.com/watch?v=fbSYZFZCFS0 they discus this actual problem. Their main suggestions are using UUIDs so the ID can be generated by the controller, or using an asynchronous method to poll for the new item.
They also mention the possibility of using event listeners, but argue that it's an anti-pattern in PHP.
I'm still undecided on how I'll prefer to do it.
Related
I have a piece of code that takes the user (passed into the job constructor) and notifies the user via a websocket to the job status.
It is effectively one line that needs to be added to the start of the handle method (before the job starts), one to the end of the handle method (after the jobs has completed) and then on in the fail method.
Other than adding this to each job manually, what is the best way to do this? Something like a trait, middleware etc. but I don't think either of these will work.
One way could be extending the job/command class like:
class MyJob extends Job {
public function handle() {
try {
do_stuff_at_start();
$this->process();
} catch (Exception $e) {
do_stuff_when_fails();
}
abstract public function process();
}
and all Your jobs could implement process() method that is responsible for handling logic. Just a loose idea - not sure if it fits Your needs.
In laravel 5.7 I want to be able to have multiple actions,
for example inserting user in database, sending registration Email,
sending notification, ...
I want to be able to execute these actions both sync and async.
the problem is I don't want to create Job class for every action.
each action is a php callable class.
The thing I don't understand in Laravel Job class is It receives dependencies as handle method arguments and receive It's Input which should process on,in the constructor, I think It's kind of odd.
for example when I want to call send register email action, I want to be able to do sth like :
$registerEmailAction->__invoke($user, true);
second parameter indicates whether to do this action sync or async.
Laravel Job classes are simple objects that only need to implement the ShouldQueue interface and an handle() method. You can dispatch them, or run them immediately explicitly calling the handle method. If you want to take the __invoke route you could so something like this:
class RegisterEmailAction implements ShouldQueue
{
//... more code ...
public function __invoke(User $user, bool $async)
{
$this->setUser($user);
if ($async) {
dispatch($this);
}
else {
$this->handle(); // or dispatch_now($this);
}
}
public function handle()
{
if (!$this->user) {
throw new UserNotFoundException();
}
// ... some other code ...
}
}
Since you don't want to pass the $user as a dependency in the constructor, I would suggest to check for it in the handle method, so that you get an error if some client code tries to call the handle method, without taking the __invoke route. You also may needs to use some traits, like SerializeeModels or Dispatchable (check the docs for more info).
I'm struggling with how to do this correctly following best practices. It might be difficult to explain but i'll try my best here.
I have an external API I need to make very many different calls to. So what I did was creating a class in the App folder called Api.php for now. It's using Guzzle for API calls.
In the Controller for the view I create the Api object in the needed functions and call the corresponding function in the API class.
Controller
public function uploadDevice(Request $request)
{
## Validation etc is performed
// Calling the API
$api = new Api();
$api->uploadDevice();
}
Api.php
class Api
{
private $token;
public function __construct(){}
public function checkIfHasToken(){}
public function getTokenFromSession(){}
public function getFreshToken(){}
public function uploadDevice(){}
}
Some questions
The checkIfHasToken() needs to be called before every request. Should it be done in the constructor, first in each function doing API calls or directly from the Controller?
Exceptions : Where should I do the Try/catch etc ? Should it be done in the Api class where it's needed or in the Controller by calling each and every function from the API class and wrapping it in try/catch?
Redirects : I want to redirect back to the Route the request came from with every possible errors or success message included. So if I have a try/catch I want to redirect with the result of the catch included. Where to put this logic? Redirecting from the nested function does not seem to work. So then I'm back to calling each and every function in the Api class from the Controller one by one and handle the exceptions/errors/validations separately in the Controller?
Maybe I'm thinking too much about this or making it more complicated than it needs to be. Not sure anymore.
// Controller
public function __construct(ApiService $apiService)
{
$this->api = $apiService;
}
public function uploadDevice(Request $request)
{
// Ensure that the user has a token in a custom HTTP request or in a middleware somewhere
try {
$this->api->uploadDevice();
}
catch (Exception $exception){
return redirect()->back();
//You can include errors from $exception here.
}
}
// Service
class ApiService
{
public function uploadDevice()
{
return 'I did a thing';
}
}
Explaination
Laravel has many ways to do the same thing, it is all about what you need and how you want your application to scale.
Checking if a token is present or valid should be done in a middleware.
A try catch can be anywhere depending on how much you need to see in the exception, normally just in a controller is ok, but you can
do this in many ways. I personally like to make an event listener
for any http error.
Return redirect back from the controller will be fine to always redirect to the place that invoked the controller
The checkIfHasToken() needs to be called before every request. Should
it be done in the constructor, first in each function doing API calls
or directly from the Controller?
If it needs to be called for every request, I suggest making it middleware as it's made for this purpose.
Exceptions : Where should I do the Try/catch etc ? Should it be done
in the Api class where it's needed or in the Controller by calling
each and every function from the API class and wrapping it in
try/catch?
This depends, if you want to be able to control the output when an exception occurs then you probably want it in your controller. If you can program something to do when the exception occurs (return unsuccessful for instance), do it in a lower level (api).
Redirects : I want to redirect back to the Route the request came from
with every possible errors or success message included. So if I have a
try/catch I want to redirect with the result of the catch included.
Where to put this logic? Redirecting from the nested function does not
seem to work. So then I'm back to calling each and every function in
the Api class from the Controller one by one and handle the
exceptions/errors/validations separately in the Controller?
You can go back by returning redirect()->back() as the response, the best way to show errors would to include them somewhere. I suggest using session()->flash() for this. These calls can be made from the try/catch.
I develop a pretty big web application using laravel. Logging to a single file or daily files seems too messy for me.
My purpose is to create error logs which will be categorised and easy for retrieval to show on front-end for tech support. E.g. user entered wrong api-key, email, whatever.
P.S. Users don't always understand meaning of the error showed so they contact tech support.
Example for better understanding:
Suppose I have model relations: User->hasMany->Project and in some
project appears an exception I want it to be written and related to
the project. e.t.c. So I could do something like $some_project_model->logs()->get() and tech support got all logs related to
certain project.
What I have in my mind is separate table for logs which is related to certain Eloquent models. But to do so ExceptionHandler needs to know to which model an Exception is related to.
So how this can be implemented? Maybe I could assign custom ExceptionHandler to certain class?
Thank you for any suggestions.
So I've actually found a proper solution:
First of all it's needed to create Log table with polymorphic relation. Then to catch all the exceptions in certain context __call magic method can be used. __call method allows to wrap every object method call with custom code. Try-catch in this case.
Example:
class Loggable
{
public function __call($method, $arguments)
{
try {
return call_user_func_array([$this->certain_obj, $method], $arguments);
} catch (Exception $e) {
//creating new $log orm
$this->get_orm()->loggable()->save($log);
}
}
protected function do_something() {
//something is going on
throw new \Exception();
}
}
$loggable_obj = new Loggable();
$loggable_obj->do_something();
To make it work, you must make all loggable methods private or protected.
In case if you think that 'encapsulation' is not just another difficult word you can achieve the same result using proxy class.
Example:
class Proxy
{
private $some_obj;
public function __construct($some_obj)
{
$this->some_obj = $some_obj;
}
public function __call($method, $arguments)
{
try {
return call_user_func_array([$this->some_obj, $method], $arguments);
} catch (Exception $e) {
//creating new $log orm
$this->some_obj->get_orm()->loggable()->save($log);
}
}
}
$proxy = new Proxy($some_obj);
$proxy->whatever_method($foo, $bar);
So, in both cases I can get all logs related to some exact orm by calling ->logs()->get() on it.
Not a rocket science at all.
One approach might be to create your own custom exception (sub)class which you could add model and/or project information to at the point of throwing the exception. Then this information would be available in the ExceptionHandler.
You wouldn't have it for built in or third-party exception types though. For those you'd end up having to catch and rethrow where possible (and necessary), wrapping in your custom exception class, which probably isn't ideal.
Consider the following method:
function m1()
{
$ent = new Entity;
...
try {
$ent->save();
} catch (QueryException $e) {
...
}
I've got to trigger an exception. Preferably with mockery. How do I do that?
P.S. I can't pass $ent into the method.
UPD Let me describe my particular case to confirm if I do need to trigger an exception. Here I'm trying to test controller's action that is triggered by payment system to notify that user has made a payment. In it I, among other things, store in database all the data coming from payment system in PaymentSystemCallback model, and link it to Order model, which is created before redirecting user to the payment system. So, it goes like this:
function callback(Request $request)
{
$c = new PaymentSystemCallback;
$c->remote_addr = $request->ip();
$c->post_data = ...;
$c->headers = ...;
...
$c->save();
$c->order_id = $request->request->get('order_id');
$c->save();
}
But if incorrect order_id comes in, foreign constraint fails, so I change it this way:
try {
$c->save();
} catch (QueryException $e) {
return response('', 400);
}
But it doesn't look good to handle any database exception this way, so I'm seeking for a way to rethrow the exception unless $e->errorInfo[1] == 1452.
And here's what I came up with:
/**
* #runInSeparateProcess
* #preserveGlobalState disabled
*/
function testExceptionOnSave()
{
$this->setUpState();
Mockery::mock('overload:App\PaymentSystemCallback')
->shouldReceive('save')
->andReturnUsing(function() {}, function() {
throw new QueryException('', [], new Exception);
});
$this->doRequest();
$this->assertBalanceDidntChange();
$this->assertNotProcessed();
$this->seeStatusCode(500);
}
I use #runInSeparateProcess because preceding tests trigger the same action, and therefore the class is loaded before mockery has a chance to mock it.
As for #preserveGlobalState disabled it doesn't work without it. As phpunit's documentation put it:
Note: By default, PHPUnit will attempt to preserve the global state from the parent process by serializing all globals in the parent process and unserializing them in the child process. This can cause problems if the parent process contains globals that are not serializable. See the section called “#preserveGlobalState” for information on how to fix this.
I deviate a little from what mockery's documentation says when I'm marking only one test to run in a separate process, since I need it only for one test. Not the whole class.
Constrictive criticism is welcome.
The easiest way around this is to call a factory method that creates a mock instance of your Entity. Something like:
function testSomething()
{
$ent = $this->getEntity();
...
try {
$ent->save();
} catch (QueryException $e) {
...
}
}
function getEntity()
{
$mock = $this->createMock(Entity::class);
$mock
->method('save')
->will($this->throwException(new QueryException));
return $mock;
}
Your method is not designed for test. Fix that. If you can't, then you have to monkey patch, which PHP does not support natively.
My recommended approach would be to have your test suite install its own priority autoloader. Have your test case register a mock class into that autoloader, associated with class name Entity. Your mock class will do its magic to throw an exception. If you're using PHP 7, you have access to anonymous classes, which makes fixtures easier: new class Entity {}.
Per the accepted answer, Mockery supports this autoloading trick using the overload: quantifier on mocked classes. This saves a lot of work on your part!