How to call Service class function? - laravel

use App\Http\Controllers\ATransaction;
use App\Service\ATransactionServices;
class TransactionController extends Controller {
public function ATransaction(Request $request, ATransactionServices $ATransaction){
try {
$validated_request = $this->validateRequest($request);
} catch (ValidationException $exception) {
return redirect()->back()->withInput()->withErrors($exception->errors());
}
How to call my service class function to replace into
$validated_request = $this->validateRequest($request);
Can someone show me the right way?

You can traits concepts or can directly use DI ( dependency Injection ) to implement service into this class.
For traits, you can check How to create traits in Laravel
And if you want to use Dependency Injection then do something like:
public function __construct(ATransactionServices $aTransactionServices)
{
$this->aTransactionServices = $aTransactionServices;
}
Then inside the method you can directly use the service methods:
$this->aTransactionServices->methodName();
If you want to use method inside the try and catch than simplest way is to add them in controller.
class TransactionController extends Controller {
//This will help in directly injecting the service into the container
public function __construct(ATransactionServices $aTransactionServices)
{
$this->aTransactionServices = $aTransactionServices;
}
//Otherwise you can create a custom request to handle validations on the request level
public function ATransaction(Request $request){
try {
$validated_request = $this->aTransactionServices->validateRequest($request);
//Here you can setup the validation mechanism like custom status based validation e.g. $validated_request['status'] which can be true or false otherwise use validator instance to use $validated_request->fails() to check
if($validated_request->fails()) {
//Here pass the data and you can handle on catch how you want to send response
throw new Exception('Exception details or validator instance');
}
} catch (Exception $exception) {
return redirect()->back()->withInput()->withErrors($exception->errors());
}
}
But if your only purpose is to handle the validations than you can use custom request in laravel for validations handling: Custom Request
Hope this works for you and resolves your issue.
have a great day.

$validated_request = $ATransaction->validateRequest($request);

Related

Passing data from a route to determine dependency injection in controller constructor (Laravel 9)

Currently, I have a service that fulfills a contract promise to our resource controller's constructor parameter list, however the contract that is provided to the controller from the service is based upon the route that is being accessed (the contract is for an action file.)
Because of that, I currently have a switch statement reading the route and making logic choices off of that in the service file. I would much prefer to have something akin to middleware handle the action contract choice, in the same way that the router provides a controller file. As you may already know, Laravel now loads in the controller's constructor before any middleware is loaded, and I need a new way of tying the route to the action, if possible.
Example set up:
// WHAT I HAVE:
// Router
Route::apiResource('foo', App\Http\Controllers\FooController::class);
Route::apiResource('bar', App\Http\Controllers\BarController::class);
Route::apiResource('foo-bar', App\Http\Controllers\FooBarController::class);
// Service
...
switch ($this->getResourceNameFromRoute()) {
case 'foo':
switch ($request->getMethod()) {
case 'GET':
return new FooShowActionContract();
case 'POST':
return new FooUpdateActionContract();
...
}
case 'bar':
...
}
// FooController
public function __construct(?ActionContract $action = null)
{
$this->action = $action;
}
how i would have like to solve this (but obviously, this will never work):
// Router
Route::apiResource('foo-bar', App\Http\Controllers\FooBarController::class)->middleware('actionMap:FooBarActionMap::class');
// Service
...
public function resolveContract()
{
return app()->make(ActionContract::class, $request);
}
// FooController
public function __construct(?ActionContract $action = null)
{
$this->action = $action;
}
// Middleware
public function handle($request, $next, $actionClassName)
{
// bind an instance of the passed in class to the app for service to instantiate
}
Do you know of a way to do this, where I can pass in the contract class name from the route call? There has to be something more elegant than a switch statement that I'm not thinking of.

Mocking a service class inside controller

I am trying to write a Feature test for my controller. To simplify my current situation, imagine my controller looks like this:
public function store(Business $business)
{
try {
(new CreateApplicationAction())->execute($business);
} catch (Exception $e) {
return response()->json(['message' => 'error'], 500);
}
return response()->json(['message' => 'success']);
}
What I am trying to achieve is, instead of testing CreateApplication class logic inside my integration test, I want to write another unit test for it specifically.
Is there a way I can simply say CreateApplicationAction expects execute() and bypass testing inside it? (without executing the code inside execute())
/** #test */
public function can_create_application()
{
$business = Business:factory()->create();
$mock = $this->mock(CreateApplicationAction::class, function (MockInterface $mock) use ($business) {
$mock->shouldReceive('execute')
->once()
->with($business)
->andReturn(true);
});
$response = $this->post('/businesses/3/application', $data);
$response->assertOk();
}
I saw online that people create "MockCreateApplicationAction" class but if possible I don't want to create another class as I don't want any logic to be inside it at all.
Is it possible?
class CreateApplicationAction
{
public function execute($business) {
dd("A");
// Business Logic...
}
}
So when I do the Mock, dd() should never be called. Or I am going in the wrong direction?
You will need to use Laravels container to resolve your class. The basic approach is to use the resolve() method helper. PHP does not have dependency injection, so you need to use one to make it possible, in Laravel the container solves that.
resolve(CreateApplicationAction::class)->execute($business);
On constructors, controller methods, jobs, events, listeners and commands (rule of thumb if the method is named handle), you can inject classes into the parameters and they will resolve through the container.
public function store(Business $business, CreateApplicationAction $applicationAction)
{
try {
$applicationAction->execute($business);

Laravel unit testing automatic dependency injection not working?

With Laravel Framework 5.8.36 I'm trying to run a test that calls a controller where the __construct method uses DI, like this:
class SomeController extends Controller {
public function __construct(XYZRepository $xyz_repository)
{
$this->xyz_repository = $xyz_repository;
}
public function doThisOtherThing(Request $request, $id)
{
try {
return response()->json($this->xyz_repository->doTheRepoThing($id), 200);
} catch (Exception $exception) {
return response($exception->getMessage(), 500);
}
}
}
This works fine if I run the code through the browser or call it like an api call in postman, but when I call the doThisOtherThing method from my test I get the following error:
ArgumentCountError: Too few arguments to function App\Http\Controllers\SomeController::__construct(), 0 passed in /var/www/tests/Unit/Controllers/SomeControllerTest.php on line 28 and exactly 1 expected
This is telling me that DI isn't working for some reason when I run tests. Any ideas? Here's my test:
public function testXYZShouldDoTheThing()
{
$some_controller = new SomeController();
$some_controller->doThisOtherThing(...args...);
...asserts...
}
I've tried things like using the bind and make methods on app in the setUp method but no success:
public function setUp(): void
{
parent::setUp();
$this->app->make('App\Repositories\XYZRepository');
}
That's correct. The whole idea of a unit test is that you mock the dependant services so you can control their in/output consistently.
You can create a mock version of your XYZRepository and inject it into your controller.
$xyzRepositoryMock = $this->createMock(XYZRepository::class);
$some_controller = new SomeController($xyzRepositoryMock);
$some_controller->doThisOtherThing(...args...);
This is not how Laravels service container works, when using the new keyword it never gets resolved through the container so Laravel cannot inject the required classes, you would have to pass them yourself in order to make it work like this.
What you can do is let the controller be resolved through the service container:
public function testXYZShouldDoTheThing()
{
$controller = $this->app->make(SomeController::class);
// Or use the global resolve helper
$controller = resolve(SomeController::class);
$some_controller->doThisOtherThing(...args...);
...asserts...
}
From the docs :
You may use the make method to resolve a class instance out of the
container. The make method accepts the name of the class or interface
you wish to resolve:
$api = $this->app->make('HelpSpot\API');
If you are in a location of your code that does not have access to the
$app variable, you may use the global resolve helper:
$api = resolve('HelpSpot\API');
PS:
I am not really a fan of testing controllers like you are trying to do here, I would rather create a feature test and test the route and verify everything works as expected.
Feature tests may test a larger portion of your code, including how
several objects interact with each other or even a full HTTP request
to a JSON endpoint.
something like this:
use Illuminate\Http\Response;
public function testXYZShouldDoTheThing()
{
$this->get('your/route')
->assertStatus(Response::HTTP_OK);
// assert response content is correct (assertJson etc.)
}

Managing views and api from the same controller

I have an issue with my app, that is "hybrid", what I mean by "hybrid" controllers have to manage both views ans APIs.
So, basically, for each controller, I must check:
if $request->wantsJson(){
... // Client rendering using Angular, return json
}else{
// Server rendering Using blade, return view
}
I don't like the fact to have a conditional in every controller method.
I also wouldn't like to have a API folder with a copy of all my controller, there would be a lot of duplicated code.
How should I do it?
I would suggest to create a separate class to handle output ex: class ResultOutput with the method, output.
So, in your controller, when you are ready to output your data, just create a new instance of ResultOutput class, and call method output with relevant data.
In ResultOutput class, inject Request object so you can determine the method of the output based on above logic.
Ex: In your controller:
return (new ResultOutput())->output($data);
In ResultOutput class:
class ResultOutput()
{
private $type;
public __construct(Request $request) {
$this->output = 'view';
if ($request->wantsJson()) {
$this->output = 'json';
}
}
public method output($data) {
if ($this->type =='view') {
// return the view with data
} else {
// return the json output
}
}
}
In this way, if you need to introduce new output method (ex: xml), you can do it without changing all your controllers.

Laravel inject sentry user into model

I keen to make my code decouple and ready for testing.
I have an Eloquent model getBudgetConvertedAttribute is depend on sentry user attribute.
public function getBudgetConvertedAttribute()
{
return Sentry::getUser()->currency * $this->budget;
}
This throw error while testing because Sentry::getUser is return null.
My question is, How shall I code to inject user into model from controller or service provider binding or testing?
Inject a $sentry object as a dependency in the constructor instead of using the Sentry Facade.
Example
use Path\To\Sentry;
class ClassName
{
protected $sentry
public function __construct(Sentry $sentry)
{
$this->sentry = $sentry;
}
public function methodName()
{
$this->sentry->sentryMethod();
}
}
Why not just create a method on the model, then takes a Sentry user object as a parameter?
public function getBudgetConverted(SentryUser $user)
{
return $user->currency * $this->budget;
}
You’ll need to change the type-hint (SentryUser) to the actual name of your user class.
If this is to aid testing, you could go one step furhter and type-hint on an interface (which you should be any way), that way you could test your method with a mock user object rather than one that may have a load of other dependencies like a database connection, which Eloquent models do.

Resources