I'm using FOS Rest Bundle for my RESTful API, I want to override the responses returned from my ApiController, example:
php
/**
* #QueryParam(name="sourceAddr", strict=true, requirements="[0-9]{8}", description="Do things")
* #param $paramFetcher ParamFetcher
* #return array
* #throws MtBoxApiException
*/
public function getAuthAction(ParamFetcher $paramFetcher)
{
return [
'rfid' => '445545454',
'fName' => 'adazda',
'lName' => '8888888',
'prod' => 75
];
}
What I want is adding additional data to the returned responses, so I want to intercept these responses and override them based on some conditions.
The final result that I want the api returns:
{
someAdditionalData1: value1,
someAdditionalData2: value2,
data: {
//the data returned by the controller action
}
}
For an idea how this is done you can look at the FOS\RestBundle\EventListener\ViewResponseListener class. They register this class as an event subscriber. You can register your class in the same way in your services.yml
test_response_listener:
class: MyBundle\EventListener\MyViewResponseListener
tags:
- { name: kernel.event_subscriber }
You need to ensure your class implements Symfony\Component\EventDispatcher\EventSubscriberInterface and contains the method getSubscribedEvents like this:
public static function getSubscribedEvents()
{
return array(
KernelEvents::VIEW => array('onKernelView', 50),
);
}
The event is 'onKernelView', i.e. when a view is returned. This will only be called if the response from the controller is not actually a Response object. In my test of this I returned a User object so it was called. The "50" represents the priority. If you don't put it higher than 30 then the FOSRestBundle listener will be called first and will set the response. If any of these listeners call $event->setResponse then the other ones are ignored so make sure you don't do it in your method or the FOSRest one won't be called.
The onKernelView is the name of the method to be called. It will receive a certain type of event so make your method signature like this:
public function onKernelView(GetResponseForControllerResultEvent $event)
Now, finally, what you want to do is unify the response format. You can do this by changing the controller result of the event inside your listener method to match the format you want:
$event->setControllerResult([
'foo' => 'bar',
'data' => $event->getControllerResult()
]);
If you have the serializer set up it should still serialize your controller result as normal, but you'll get the added data in the response.
Related
I registered LogConnectionFailed like this:
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
'Illuminate\Http\Client\Events\ConnectionFailed' => [
'App\Listeners\LogConnectionFailed',
],
];
The ConnectionFailed event is fired if no response is received for a given request.
my class {
public function send() {
$response = Http::get('http://example.com');
}
}
I need to The name of the class and the method in which this happened and duration time call http client in LogConnectionFailed class.
This is not possible through normal parameter passing, so I utilized PHP native function debug_backtrace() and hacked through it.
The logic is that when the listener wants to handle the event, we get the callback trace and filter through the call stack frames until we find one of our watching location.
The code is this:
use Illuminate\Support\Str;
use Illuminate\Http\Client\Events\ConnectionFailed;
class LogConnectionFailed
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
public function handle(ConnectionFailed $event)
{
$backtraceCollection = Collect(debug_backtrace());
$callerClass = $backtraceCollection->first(function($value, $key) use ($event){
$class = $value['class'] ?? '';
return $this->classIsWatched($class);
});
if ($callerClass) {
// Store in DB or do some other stuff.
dd([
'class' => $callerClass['class'],
'function' => $callerClass['function'],
'line' => $callerClass['line'],
]);
} else {
dd("should skip. Not Watching classes.");
}
}
private function classIsWatched(string $className): bool
{
return Str::is(
['App\Http\Controllers\*', 'App\MyClass'],
$className
);
}
}
Here take note at the array inside the function classIsWatched:
['App\Http\Controllers\*', 'App\MyClass']
These are the classes or directories we will watch, which means if the ConnectionFailed due to some calls from these classes, they will be captured, else they will be skipped. This gives you the flexibility to just filter out and watch certain locations inside your application.
Note that we can also use wildcards * for simplifying the path inclusions. For example App\Http\Controllers\Api\EventController is watched too.
For example if I have this class inside the App path:
namespace App;
use Illuminate\Support\Facades\Http;
class MyClass
{
public static function callEvent()
{
$response = Http::get('http://example.com');
}
}
due to any reason if a ConnectionFailed event dispatches, the output of handle method will be:
array:3 [▼
"class" => "App\MyClass"
"function" => "callEvent"
"line" => 11
]
this will give you the class name, function name and even the line which event was raised there. You can simply replace the dd() inside the handle method of the listener and do what you want to do with the data.
About the Http Call duration, no accurate solution comes to my mind, but you can have a rough estimation using this approach:
dd(microtime(true) - LARAVEL_START);
add the above code inside the handle method too, and this gives you the time difference from the moment that the application started and till you got this point (Http request failed and you got inside this listener).
I am making a REST API that will return different JSON responses depending on which type of User is making the call.
There is a single endpoint: example.com/api/v1/collect that uses Laravel's API authentication to get the User model with $user = auth()->guard('api')->user();.
Each User will belong to a Type.
If User 1 (type_id 1) makes the call, the response will look like:
{
"example": 1,
"success": true,
"data" : [
...
]
}
If User 2 (type_id 2) makes the call, the response can be different, depending on the user's type. It could look like:
{
"example": 2,
"response" : [
...
],
"custom": "string",
"success": 200
}
The ... is the data that we are sending back (for example a list of Post titles) and it will always be the same, but the "envelope" (or wrapper) around it would be specific to each user (or type of user).
So far, I've found two solutions to wrap that ... in an abstracted way:
Solution 1: Using Laravel Blade
// Api\V1\ApiController.php
$data = $user->posts->pluck('title');
// Each type of user will have a different blade filename
// There could be around a 100 types which will result in a 100 blade files
// The filename is stored in the database
$filename = $user->type->filename; // returns 'customBladeTemplate'
// Return a JSON response after passing the $data to the view
return response()->json([
view($filename, compact('data'))->render(),
]);
Using a blade file for each type of user allows me to wrap the data like this:
// resources/views/customBladeTemplate.blade.php
// This filename has to match the one in the database column
{
"example": 1,
"success": true,
"data" : [
{!! $data !!}
]
}
That will output a JSON response for the User 1 (example 1)
Solution 2: Using Laravel response macros
// Api\V1\ApiController.php
$data = $user->posts->pluck('title');
// Each type of user will have a different macro name
// There could be around a 100 types which will result in a 100 different macros
// The macro name is stored in the database
$macroName = $user->type->macro_name; // returns 'customMacroName'
return response()->{macroName}($data);
Creating a Macro for each type of user, using the macro name from the DB:
// App\Providers\AppServiceProvider.php
use Illuminate\Http\Response;
public function boot()
{
Response::macro('customMacroName', function ($data) {
return Response::json([
'example' => 2,
'response' => $data,
'custom' => 'string',
'success' => 200,
]);
});
}
That macro will output a JSON response for the User 2 (example 2)
Both options work fine but I am still wondering:
Is there another (possibly better) way to do it?
Are those two solutions valid or can they be enhanced?
Which of those two solutions seem to be better and why?
Edit: The $data is not actually coming from an eloquent model, it is rather from a serialized JSON column (JSON casting) - which means I can't use the Laravel API resources
If you are looking for the response formatting you should go with the Laravel API Resources
Based on your requirement(data formate different for two type of users), you can create two different Api Resource classes.
AdminResource & UserResource.
Here you have more flixibility on controlling fields or orgnizing data.
Here is how you can define the resource class:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class UserResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
And you can use this as:
use App\User;
use App\Http\Resources\UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
If you would like to include with condition check in with user type, you can create a common function called renderJson($userType, $data) and place this in your parent class or can wrap with traits, all depends on your application architecture.
Here you can find laravel documentation for API Resource: https://laravel.com/docs/5.8/eloquent-resources
Edited:
With Laravel API Resource, you not only parse the modal object, you can parse any arrayble object.
Essentially they are just simple objects with one very important job
to do — transform your objects (interesting I said objects and not
models). To do this out of the box, all you have to do is instantiate
the Resource (collection or individual) with an Arrayable object. If
you did nothing else but generate a standard Resource and pass in an
Arrayable object the Resource would transform that object
automatically, and because Models are Arrayable this is where I got
caught out because if you create a resource collection and instantiate
it with a collection of models then the models get toArray'd and not
their corresponding resource.
Src: https://medium.com/#dinotedesco/laravel-api-resources-what-if-you-want-to-manipulate-your-models-before-transformation-8982846ad22c
So in your case if you can just collect() the json data and pass to api resource.
You could use middlewares to change what the response looks like.
With middleware you could change the response after the execution of your regular code, without having to take this into account in the controller itself. Using the below code you modify the response AFTER it has been executed.
<?php
namespace App\Http\Middleware;
use Closure;
class AfterMiddleware
{
public function handle($request, Closure $next)
{
// Calls the controller and processes the request.
$response = $next($request);
// Here you can retrieve the user and modify the response however you want.
// Some example code:
$user = Auth::user();
if ($user->type == 1) {
... //Change response for user type 1
}
if ($user->type == 2) {
... //Change response for user type 2
}
// Etc...
return $response;
}
}
Reference: https://laravel.com/docs/5.8/middleware
Depends on how different the responses are from each other. I'm inclined to take inventory of each type's common features and build a response array as appropriate. This could be done in the controller or a helper function and then returned using Laravel's JSON response type.
$response = [];
// results common to all types
$response['example'] = $example;
$response['success'] = $success;
// customized results for specific types
if (in_array($type, [1, 3, 4, 5, ...])) {
$response['data'] = $dotdotdot;
}
if (in_array($type, [2, 6, 7, 8, ...])) {
$response['response'] = $dotdotdot;
$response['custom'] = $custom;
}
return response()->json($response);
I don't know if this is what you are looking for. I had something similiar a few months ago and fixed it with json files. As json is amazingly fast and you can create thousands of types.
Sorry for my bad english i will fix it after the weekend :-)
Let's get started.
First the user logs in using laravel passport or api routes.
Second the api calls a controller.(class). I will create a class based on your info.
let's say the api calls the ApiController and the method handle
use Illuminate\Http\Request;
class ApiController
{
public function __construct()
{
}
/**
* Handle the incoming request
*
* #param Request $request
*/
public function handle(Request $request)
{
//first let's find the correct format
$type = $requets->user()->type; //lets say type_1
$config = $this->getUserType($type);
//i don't know where you data comes from but let's say $data is your data.
$data = json_encode(['some' => "data", 'to' => "return"]);
//lets fill the data
$response = $this->fillDataInConfig($data, $config);
return response()->json($response);
}
/**
* Find the user type config by type name
*
* #param string $type
* #return object
*/
private function getUserType(string $type) : string
{
//let store them in the local storage
$config = \Storage::disk('local')->get("api_types/$type.json");
return json_decode($config);
}
/**
* Fill the data
*
* #param mixed $data
* #param object $config
* #return object
*/
private function fillDataInConfig($data, object $config) : object
{
//as for your example. The reusl//
// {
// "example": 2,
// "response" : *responseData*, <===== where the response should be
// "custom": "string",
// "success": 200
// }
foreach($config as $index => $value){
if($value === '*responseData*'){
$config->{$idnex} = $data;
}
}
//the data is filled in the response index
return $config;
}
}
Using Laravel framework and it's REPL named Tinker in my project, I want to set the request object to the same state it would be if I made some real HTTP request through my browser.
When I dump the request using
dd($request);
I receive a lot of data in $request like headers, form input data, and so on.
I want to receive the same data in $request in Tinker REPL.
How can I emulate HTTP request in Tinker from the command line?
You should be able to instantiate a request object and then use replace to get some input data into it. Something like this should work in tinker...
>> $r = new Illuminate\Foundation\Http\FormRequest()
>> $r->replace(['yada' => 'bla bla bla'])
>> $r->yada
That should output bla bla bla.
Request class has some set of methods to initiate it with names begins from create... And the create method allow to initiate it with manually gived params like url, method and additional optional parameters:
Illuminate\Foundation\Http\FormRequest::create('http://your-url', 'METHOD', ...)
so you can use it from REPL to play with your controllers and initiate them like you come from some route
I know this question is pretty old but I recently wanted to test a controller POST and found it difficult to find an answer, so here is my solution:
I created a formRequest class which I wanted to test in Tinker. The class looks something like this:
<?php
namespace App\Http\Requests;
use App\Models\Task;
use Carbon\Carbon;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
class TaskRequest extends FormRequest
{
private $_validator;
private $_task;
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
// public function authorize()
// {
// return false;
// }
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'task_name' => ['required', 'max:100'],
'task_description' => ['nullable', 'max:1024'],
'due_date' => ['nullable', 'max:50','after:today'],
// ... and more
];
}
public function validator()
{
return $this->_validator ?? $this->_validator = Validator::make($this->sanitize(), $this->rules());
}
/**
* Convert any of your incoming variables, such as dates to required format
**/
public function sanitize()
{
// get request vars
$vars = $this->all();
if ( !empty($vars['due_date']) )
{
$date = new Carbon($vars['due_date'] );
$vars['due_date'] = $date->toDateTimeString();
}
return $vars;
}
public function save()
{
if ( $this->validator()->fails() )
return false;
return $this->_task = Task::create(
$this->validator()->validated()
);
}
In tinker:
$form = new App\Http\Requests\TaskRequest();
$form->merge(['task_name'=>'test task','due_date'=>'2022-04-22Z15:45:13UTC']);
$form->validator()->validated();
=> [
"task_name" => "test task",
"due_date" => "2022-04-22 15:45:13"
]
I have a custom class in Laravel that tracks the analytics of my app through Segment (using this package for php: https://github.com/AltThree/Segment).
Here is a snippet of my class and a function I am calling through my listener to track a login:
class Tracking {
private function segmentTrack(User $user, string $event, array $properties = null) {
$segment = Segment::track([
"userId" => $user->id,
"event" => $event,
"properties" => $properties
]);
dd($segment);
}
/**
* Handle Login tracking
*
* #param User $user
* #return void
*/
public function login (User $user) {
$this->segmentTrack($user, "Login");
}
}
Notice the dd in the segmentTrack function. When I run the Laravel queue and I then trigger the Tracking->login() event through my app, the listener goes off fine and with the dd function, it will send that data to Segment and I can see it in their live debugger, all is well.
However, when I remove that dd, and the listener goes off and shows as successful - the data is never seen in Segment.
Can someone tell me what i'm missing? This is my first time using the Laravel queue system so a little confused why it might not be working.
For queued jobs, use:
Segment::track($payload);
Segment::flush();
I'm trying to create unit tests to test some specific classes. I use app()->make() to instantiate the classes to test. So actually, no HTTP requests are needed.
However, some of the tested functions need information from the routing parameters so they'll make calls e.g. request()->route()->parameter('info'), and this throws an exception:
Call to a member function parameter() on null.
I've played around a lot and tried something like:
request()->attributes = new \Symfony\Component\HttpFoundation\ParameterBag(['info' => 5]);
request()->route(['info' => 5]);
request()->initialize([], [], ['info' => 5], [], [], [], null);
but none of them worked...
How could I manually initialize the router and feed some routing parameters to it? Or simply make request()->route()->parameter() available?
Update
#Loek: You didn't understand me. Basically, I'm doing:
class SomeTest extends TestCase
{
public function test_info()
{
$info = request()->route()->parameter('info');
$this->assertEquals($info, 'hello_world');
}
}
No "requests" involved. The request()->route()->parameter() call is actually located in a service provider in my real code. This test case is specifically used to test that service provider. There isn't a route which will print the returning value from the methods in that provider.
I assume you need to simulate a request without actually dispatching it. With a simulated request in place, you want to probe it for parameter values and develop your testcase.
There's an undocumented way to do this. You'll be surprised!
The problem
As you already know, Laravel's Illuminate\Http\Request class builds upon Symfony\Component\HttpFoundation\Request. The upstream class does not allow you to setup a request URI manually in a setRequestUri() way. It figures it out based on the actual request headers. No other way around.
OK, enough with the chatter. Let's try to simulate a request:
<?php
use Illuminate\Http\Request;
class ExampleTest extends TestCase
{
public function testBasicExample()
{
$request = new Request([], [], ['info' => 5]);
dd($request->route()->parameter('info'));
}
}
As you mentioned yourself, you'll get a:
Error: Call to a member function parameter() on null
We need a Route
Why is that? Why route() returns null?
Have a look at its implementation as well as the implementation of its companion method; getRouteResolver(). The getRouteResolver() method returns an empty closure, then route() calls it and so the $route variable will be null. Then it gets returned and thus... the error.
In a real HTTP request context, Laravel sets up its route resolver, so you won't get such errors. Now that you're simulating the request, you need to set up that by yourself. Let's see how.
<?php
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
class ExampleTest extends TestCase
{
public function testBasicExample()
{
$request = new Request([], [], ['info' => 5]);
$request->setRouteResolver(function () use ($request) {
return (new Route('GET', 'testing/{info}', []))->bind($request);
});
dd($request->route()->parameter('info'));
}
}
See another example of creating Routes from Laravel's own RouteCollection class.
Empty parameters bag
So, now you won't get that error because you actually have a route with the request object bound to it. But it won't work yet. If we run phpunit at this point, we'll get a null in the face! If you do a dd($request->route()) you'll see that even though it has the info parameter name set up, its parameters array is empty:
Illuminate\Routing\Route {#250
#uri: "testing/{info}"
#methods: array:2 [
0 => "GET"
1 => "HEAD"
]
#action: array:1 [
"uses" => null
]
#controller: null
#defaults: []
#wheres: []
#parameters: [] <===================== HERE
#parameterNames: array:1 [
0 => "info"
]
#compiled: Symfony\Component\Routing\CompiledRoute {#252
-variables: array:1 [
0 => "info"
]
-tokens: array:2 [
0 => array:4 [
0 => "variable"
1 => "/"
2 => "[^/]++"
3 => "info"
]
1 => array:2 [
0 => "text"
1 => "/testing"
]
]
-staticPrefix: "/testing"
-regex: "#^/testing/(?P<info>[^/]++)$#s"
-pathVariables: array:1 [
0 => "info"
]
-hostVariables: []
-hostRegex: null
-hostTokens: []
}
#router: null
#container: null
}
So passing that ['info' => 5] to Request constructor has no effect whatsoever. Let's have a look at the Route class and see how its $parameters property is getting populated.
When we bind the request object to the route, the $parameters property gets populated by a subsequent call to the bindParameters() method which in turn calls bindPathParameters() to figure out path-specific parameters (we don't have a host parameter in this case).
That method matches request's decoded path against a regex of Symfony's Symfony\Component\Routing\CompiledRoute (You can see that regex in the above dump as well) and returns the matches which are path parameters. It will be empty if the path doesn't match the pattern (which is our case).
/**
* Get the parameter matches for the path portion of the URI.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function bindPathParameters(Request $request)
{
preg_match($this->compiled->getRegex(), '/'.$request->decodedPath(), $matches);
return $matches;
}
The problem is that when there's no actual request, that $request->decodedPath() returns / which does not match the pattern. So the parameters bag will be empty, no matter what.
Spoofing the request URI
If you follow that decodedPath() method on the Request class, you'll go deep through a couple of methods which will finally return a value from prepareRequestUri() of Symfony\Component\HttpFoundation\Request. There, exactly in that method, you'll find the answer to your question.
It's figuring out the request URI by probing a bunch of HTTP headers. It first checks for X_ORIGINAL_URL, then X_REWRITE_URL, then a few others and finally for the REQUEST_URI header. You can set either of these headers to actually spoof the request URI and achieve minimum simulation of a http request. Let's see.
<?php
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
class ExampleTest extends TestCase
{
public function testBasicExample()
{
$request = new Request([], [], [], [], [], ['REQUEST_URI' => 'testing/5']);
$request->setRouteResolver(function () use ($request) {
return (new Route('GET', 'testing/{info}', []))->bind($request);
});
dd($request->route()->parameter('info'));
}
}
To your surprise, it prints out 5; the value of info parameter.
Cleanup
You might want to extract the functionality to a helper simulateRequest() method, or a SimulatesRequests trait which can be used across your test cases.
Mocking
Even if it was absolutely impossible to spoof the request URI like the approach above, you could partially mock the request class and set your expected request URI. Something along the lines of:
<?php
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
class ExampleTest extends TestCase
{
public function testBasicExample()
{
$requestMock = Mockery::mock(Request::class)
->makePartial()
->shouldReceive('path')
->once()
->andReturn('testing/5');
app()->instance('request', $requestMock->getMock());
$request = request();
$request->setRouteResolver(function () use ($request) {
return (new Route('GET', 'testing/{info}', []))->bind($request);
});
dd($request->route()->parameter('info'));
}
}
This prints out 5 as well.
I ran into this problem today using Laravel7 here is how I solved it, hope it helps somebody
I'm writing unit tests for a middleware, it needs to check for some route parameters, so what I'm doing is creating a fixed request to pass it to the middleware
$request = Request::create('/api/company/{company}', 'GET');
$request->setRouteResolver(function() use ($company) {
$stub = $this->createStub(Route::class);
$stub->expects($this->any())->method('hasParameter')->with('company')->willReturn(true);
$stub->expects($this->any())->method('parameter')->with('company')->willReturn($company->id); // not $adminUser's company
return $stub;
});
Since route is implemented as a closure, you can access a route parameter directly in the route, without explicitly calling parameter('info'). These two calls returns the same:
$info = $request->route()->parameter('info');
$info = $request->route('info');
The second way, makes mocking the 'info' parameter very easy:
$request = $this->createMock(Request::class);
$request->expects($this->once())->method('route')->willReturn('HelloWorld');
$info = $request->route('info');
$this->assertEquals($info, 'HelloWorld');
Of course to exploit this method in your tests, you should inject the Request object in your class under test, instead of using the Laravel global request object through the request() method.
Using the Laravel phpunit wrapper, you can let your test class extend TestCase and use the visit() function.
If you want to be stricter (which in unit testing is probably a good thing), this method isn't really recommended.
class UserTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function testExample()
{
// This is readable but there's a lot of under-the-hood magic
$this->visit('/home')
->see('Welcome')
->seePageIs('/home');
// You can still be explicit and use phpunit functions
$this->assertTrue(true);
}
}