laravel: http request gave error - php

My idea is to send https request to all the URLs saved in my database using a model called Notifications.
class guzzleController extends Controller
{
public function guzzle() {
$client = new Client();
$notes=Notification::all();
$response = $client->get($notes);
$response=$response->getStatusCode();
var_dump($response);
}
}
For some reason the get method expects string, and it gave me an error:
InvalidArgumentException in functions.php line 62: URI must be a string or UriInterface
How can I fix this? Anyone with a better idea?
this is actually my notification class
namespace App;
use App\Status;
use App\Notification;
use Illuminate\Database\Eloquent\Model;
class Notification extends Model
{
protected $fillable = ['id','website_url','email','slack_channel','check_frequency','alert_frequency','speed_frequency','active'];
public function statuses(){
return $this->belongsToMany('App\Status')->withPivot('values')->withTimestamps();
}

You're just saying you're using a Client class but there's no trace of use statements here because you're not showing all the code that is needed for us to figure this out. We don't even know what are the get method parameters. My guess is that you're getting an array of Notification class entities back from this statement: $notes=Notification::all();.
So first of all you should be iterating over them and then you call the client on each one of them. But also then you may need to provide just a string to the get method. Can't say how since there's no code about the Notification class as well.
EDIT:
Given the code you provided I think you should try with something like this:
class guzzleController extends Controller
{
public function guzzle()
{
$client = new Client();
$notes = Notification::all();
foreach ($notes as $note) {
$response = $client->get($note->website_url);
$response = $response->getStatusCode();
var_dump($response);
}
}
}

As the error message says, the guzzle client's get() method accepts either a string or a UriInterface implementation. You're fetching the data from Notification model (which returns a Illuminate\Support\Collection not an array of URIs) and feed it directly to the client. You need to prep your data for the client. Something like this:
use Notification;
use GuzzleHttp\Client;
class GuzzleController extends Controller
{
public function guzzle()
{
$client = new Client();
$notes = Notification::all();
// To do this in a more Laravelish manner, see:
// https://laravel.com/docs/5.3/collections#method-each
foreach ($notes as $note) {
// Assuming that each $note has a `website_url` property
// containing the URL you want to fetch.
$response = $client->get($note->website_url);
// Do whatever you want with the $response for this specific note
var_dump($response);
}
}
}

Related

Extended Request missing data when reaching controller with type-hint

A lot of pieces to this so here's the meat. Code very slightly tweaked for brevity.
Extended class:
<?php
namespace App\Http;
use Illuminate\Http\Request as LaravelRequest;
class Request extends LaravelRequest
{
}
Middleware:
<?php
namespace App\Http\Middleware;
use App\Http\Request as CustomizedRequest;
use Closure;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Request;
class CustomizeRequest
{
protected $app;
protected $customizedRequest;
public function __construct(Application $app, CustomizedRequest $customizedRequest){
$this->app = $app;
$this->customizedRequest = $customizedRequest;
}
public function handle(Request $request, Closure $next){
$this->app->instance(
'request',
Request::createFrom($request, $this->customizedRequest);
);
return $next($this->customizedRequest);
}
}
Routes:
Route::get('/books1/{id}',[BookController::class, 'frontend1']);
Route::get('/books2/{id}',[BookController::class, 'frontend2']);
Controller:
<?php
namespace App\Http\Controllers;
use App\Models\Book;
class BookController extends Controller
{
public function frontend1(\Illuminate\Http\Request $request){
dump($request);
dump($request->all());
dump($request->route('id'));
return Book::all();
}
public function frontend2(\App\Http\Request $request){
dump($request);
dump($request->all());
dump($request->route('id'));
return Book::all();
}
}
The /books1/5?foo=bar and frontend1() path works. $request is populated as expected.
The /books2/5?foo=bar and frontend2() path is broken. $request has vast amounts of missing data, like it was instantiated with nothing.
Evidently if I type-hint my subclass instead of the more generic parent, it's causing some kind of broken instantiation. From an OO perspective I think this should be perfectly fine and I do specifically need my subclass being provided so prefer that type-hint. Is something deep within Laravel tripping this up? Is this some obscure PHP behavior I haven't seen before?
This is kind of tricky.
First of all, you need to be familiar with the service container and dependency injection. Here is the full doc: https://laravel.com/docs/8.x/container
When you type hint a class inside a controller method, Laravel will try to understand what it should do with it.
If nothing is registered inside the service container, it will try to make a new instance of it.
\Illuminate\Http\Request is bound as a singleton (https://laravel.com/docs/8.x/container#binding-a-singleton).
While a simple bind will return a new instance at each call, a singleton will always return the exact same instance.
Here is a quick demo:
\App\Models\User::class is a class that is not explicitly bound.
When you try to resolve it using the service container, it will not find it and will try to make a new instance:
$u1 = app(\App\Models\User::class);
// Searching \App\Models\User::class...
// Cannot find \App\Models\User::class...
// returning new \App\Models\User();
$u2 = app(\App\Models\User::class);
// same process again
$u3 = app(\App\Models\User::class);
// and again
// You can check these instances are indeed different by checking their hash:
dd(
spl_object_hash($u1), // 000000004af5213500000000220f0bc0 (52135)
spl_object_hash($u2), // 000000004af5213400000000220f0bc0 (52134)
spl_object_hash($u3) // 000000004af5213700000000220f0bc0 (52137)
);
But since \Illuminate\Http\Request::class is bound by Laravel, it follows a different path:
$r1 = app(\Illuminate\Http\Request::class);
// Searching \Illuminate\Http\Request::class...
// Found it! Bound as a singleton.
// returning new \Illuminate\Http\Request() and storing the
// instance in case it is required again later;
$r2 = app(\Illuminate\Http\Request::class);
// Searching \Illuminate\Http\Request::class...
// Found it and already called! Returning the stored instance ($r1)
$r3 = app(\Illuminate\Http\Request::class);
// Searching \Illuminate\Http\Request::class...
// Found it and already called! Returning the stored instance ($r1)
// Their hash are the same
dd(
spl_object_hash($u1), // 0000000011f522cf0000000077704cd1
spl_object_hash($u2), // 0000000011f522cf0000000077704cd1
spl_object_hash($u3) // 0000000011f522cf0000000077704cd1
);
Now, what's happening?
Under the hood, when a new request is made to your app and before hitting the controller method, Laravel will do a lot of things to prepare the \Illuminate\Http\Request instance.
For instance, it will setup the route resolver inside Illuminate\Routing\Router:
/**
* Return the response for the given route.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Routing\Route $route
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function runRoute(Request $request, Route $route)
{
// here
$request->setRouteResolver(function () use ($route) {
return $route;
});
//
$this->events->dispatch(new RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
Each time Laravel internally call a method like this:
protected function method(Request $request){
// do something to $request
}
$request is always the same instance, because it is bound as a singleton.
We are now in your controller.
public function frontend1(\Illuminate\Http\Request $request){
// Searching \Illuminate\Http\Request::class...
// Found it and already called!
// Returning the stored instance that has been prepared through all
// Laravel core classes
dump($request);
dump($request->all()); //well prepared
dump($request->route('id')); //well setup
return Book::all();
}
public function frontend2(\App\Http\Request $request){
// Searching \App\Http\Request::class...
// Cannot find \App\Http\Request::class...
// returning new \App\Http\Request();
dump($request);
dump($request->all()); //nothing
dump($request->route('id')); //empty
return Book::all();
}
If you are still here, how to solve this problem?
The easiest way is to use a FormRequest, initially designed to handle form validation, but if you return an empty rules array, you should be able to do everything you did with your custom \App\Http\Request instance:
<?php
namespace App\Http;
use Illuminate\Foundation\Http\FormRequest;
class Request extends FormRequest
{
public function rules()
{
return [];
}
}
Try again, everything should work fine, since this is a feature specially designed to replace the initial \Illuminate\Http\Request object.
The full doc is here: https://laravel.com/docs/8.x/validation#creating-form-requests

PSR-7 "attributes" on Response object

I'm developing using PSR-7 (with Zend Expressive). I figured out the method
ServerRequestInterface::withAttribute()
and I was wondering why the object Response doesn't have one.
I'd like to pass metadata through middlewares after processing, on "response side".
Is there somehow to pass "attributes" on Response for post-processing? What's is the best way, following the architecture guidelines, to achieve that?
Best practise is using the request object to pass data between Middleware. The response is what is going out to the client and you want to keep this clean. The request lives only on the server and you can add (sensitive data) attributes to pass around. In case something goes wrong or you return a response early before removing the custom data, then it doesn't matter since your response is "clean".
Also if you need to pass data around: The Middleware is always executed in the order it gets from the config. This way you can make sure the request object in MiddlewareX contains the data set by MiddlewareY.
UPDATE: An example on how to pass data with the a request.
Middleware 2 sets an messenger object which Middleware 4 can use to set data which is required on the way out again.
<?php
namespace Middleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class Middleware2
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{
$messenger = new Messenger();
// Do something else before next middleware
if ($next) {
$response = $next($request->withAttribute(Messenger::class, $messenger), $response);
}
// Do something with the Response after it got back
// At this point the $messenger object contains the updated data from Middleware4
return $response->withHeader('Content-Language', $locale);
}
}
Middleware 4 grabs the messenger object and updates its values.
<?php
namespace Middleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class Middleware4
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{
$messenger = $request->getAttribute(Messenger::class);
$messenger->info('going in');
// Do something else before next middleware
if ($next) {
$response = $next($request->withAttribute(FlashMessenger::class, $messenger), $response);
}
// Do something with the Response after it got back
$messenger->info('going out');
return $response->withHeader('Content-Language', $locale);
}
}
The PSR-7 specification defines attributes only for server requests. They are mainly use to store metadata deduced from the incoming request so that they could be used later when you reach your domain layer.
On the other hand, a response is usually created in the domain layer and traverses back all the middleware stack before being actually sent to the client. So metadata added to a response would have no place where they could actually be used.
I guess that if you want to pass data from a inner middleware to an outer one, the best way is to use response headers.
Not sure if this is "best practice" but another possibility is to simply inject your data object into the middlewares.
Middleware 2 has a messenger object injected and sets some data on it:
<?php
namespace Middleware;
use Interop\Http\Server\MiddlewareInterface;
use Interop\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class Middleware2
{
private $messenger;
public function __construct(Messenger $messenger)
{
$this->messenger = $messenger;
}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$this->messenger->foo = 'bar';
$response = $handler->handle($request);
if ($this->messenger->foo = 'baz') {
return $response->withHeader('Really-Important-Header', 'Baz');
}
return $response;
}
}
Middleware 4 changes the data:
<?php
namespace Middleware;
use Interop\Http\Server\MiddlewareInterface;
use Interop\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class Middleware4
{
private $messenger;
public function __construct(Messenger $messenger)
{
$this->messenger = $messenger;
}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$this->messenger->foo = 'baz';
return $handler->handle($request);
}
}
You might even use one of the middlewares as the messenger.
Caveat: You have to make sure that both classes get constructed with the same messenger object. But that seems to be the case with most dependency injection containers.

Symfony - Filter Request with FOSRestBundle and body_listener without Annotations?

I am using Symfony2 with the FOSRestBundle. Is it possible to have the functionality of the #QueryParam and #RequestParam annotations without using annotations?
I am trying to build a json api (format), so I want to allow query params like include, page, filter, fields, and sort. My ideal way to handle this would be:
Use the format_listener to detect it is json.
Use a custom body_listener json handler to process the request so that it's similar to this.
Have the controller validate the query/request params inside the action function, and throw an exception to be handled by the exception controller if it's invalid. (The body_listener would act as a helper to make extracting the data from the request easier in the controller, but the controller makes the decisions of what to do with that data.)
I'm mostly stuck on how to make a custom body_listener. I'm not sure if I would need to make a custom decoder or normalizer, and what that class might look like since they don't give any examples.
Rough code of what controller would look like:
<?php
namespace CoreBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\View\View;
use FOS\RestBundle\Context\Context;
use Psr\Http\Message\ServerRequestInterface;
class SiteController extends FOSRestController
{
public function getAction($id, ServerRequestInterface $request)
{
try {
// Validate $request. This is where the query/request
// param annotation functionality would be replaced.
} catch (Exception $e) {
throw new InvalidRequestException($e);
}
$siteService = $this->get('app.site_service');
$site = $siteService->getSite($id);
$context = new Context();
$context->setVersion($request->getVersion());
// Ex: /sites/63?fields[sites]=name,address&fields[company]=foo,bar
if ($request->hasIncludeFields()) {
$context->addAttribute('include_fields', $request->getIncludeFields()); // Or however to do this
}
$view = new View($site, 200);
$view->setContext($context);
return $view;
}
}
You can define parameters dynamically in param fetcher. It's described in documentation.
For example:
With annotations:
<?php
namespace ContentBundle\Controller\API;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Request\ParamFetcher;
class PostController extends FOSRestController
{
/**
* #QueryParam(name="sort", requirements="(asc|desc)", allowBlank=false, default="asc", description="Sort direction")
*/
public function getPostsAction(ParamFetcher $paramFetcher)
{
$sort = $paramFetcher->get('sort');
}
}
Without annotations:
<?php
namespace ContentBundle\Controller\API;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Request\ParamFetcher;
class PostController extends FOSRestController
{
public function getPostsAction(ParamFetcher $paramFetcher)
{
$sort = new QueryParam();
$sort->name = 'sort';
$sort->requirements = '(asc|desc)';
$sort->allowBlank = false;
$sort->default = 'asc';
$sort->description = 'Sort direction';
$paramFetcher->addParam($sort);
$param = $paramFetcher->get('sort');
//
}
}

Laravel ajax post with request

I can't use the Request class in laravel for a Ajax request and the input request.
I'm trying to call a ajax request to the controller and this works until I wanted to request the data that has been posted to the controller. This has somthing to do with the Request class that I use.
use Request;
This class is used by the Ajax Request
use Illuminate\Http\Request;
This is the class used to request the input.
The problem is that I cannot use them both.
public function postQuestion(Request $request) {
//dd($request->answer);
if(Request::ajax()) {
// $answer = new Answers;
// $answer->answer = $request->answer;
// $answer->description = "Test";
// $answer->Questions_id = 1;
// $answer->save();
return Response::json($request->answer);
}
}
This is my code what i've wrote.
Anyone seeing somthing familiar? Or has a answer for it?
Issue turned out to be not being able to use two classes with same namespace. For such a case, PHP provides the as keyword.
use Illuminate\Http\Request as HttpRequest;
use Some\Other\Namespace\Request;
then in code both these classes can be used. E.g. HttpRequest::method() and Request::method()
you can use?
public function postQuestion(Requests\ModelRequest $request) { //your logic }
replace your Model in Requests\ModelRequest

How to get view data during unit testing in Laravel

I would like to check the array given to a view in a controller function has certain key value pairs. How do I do this using phpunit testing?
//my controller I am testing
public function getEdit ($user_id)
{
$this->data['user'] = $user = \Models\User::find($user_id);
$this->data['page_title'] = "Users | Edit";
$this->data['clients'] = $user->account()->firstOrFail()->clients()->lists('name', 'id');
$this->layout->with($this->data);
$this->layout->content = \View::make('user/edit', $this->data);
}
//my test
public function testPostEdit (){
$user = Models\User::find(parent::ACCOUNT_1_USER_1);
$this->be($user);
$response = $this->call('GET', 'user/edit/'.parent::ACCOUNT_1_USER_1);
//clients is an array. I want to get this
//array and use $this->assetArrayContains() or something
$this->assertViewHas('clients');
$this->assertViewHas('content');
}
TL;DR; Try $data = $response->getOriginalContent()->getData();
I found a better way to do it. I wrote a function in the TestCase which returns the array I want from the view data.
protected function getResponseData($response, $key){
$content = $response->getOriginalContent();
$data = $content->getData();
return $data[$key]->all();
}
So to get a value from the $data object I simply use $user = $this->getResponseData($response, 'user');
Inside a test case use:
$data = $this->response->getOriginalContent()->getData();
Example:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class HomeTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function testExample()
{
$data = $this->response->getOriginalContent()->getData();
// do your tests on the data
}
}
Example dumping data so you can see what in data(array) passed to view:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class HomeTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function testExample()
{
$data = $this->response->getOriginalContent()->getData();
dd($data);
}
}
Should get something back like what's in image:
I managed it by doing it in a messy way. I used assertViewHas:
$this->assertViewHas('clients', array('1' => 'Client 1', '6' => 'Client2'));
So looking at how assertViewHas is implemented HERE it looks like, what the method does, is access the view's data after this call:
$response = $this->client->getResponse()->original;
In your code, the line:
$response = $this->call('GET', 'user/edit/'.parent::ACCOUNT_1_USER_1);
essentially returns the same thing as the line above it, namely a \Illuminate\Http\Response (which extends the symfony component \HttpFoundation\Response)
So, inside the assertViewHas function it looks like laravel accesses the data using $response->$key, so I would try to access the clients and 'content' variables through the $response object.
If that doesn't work try searching around the TestCase file in the Laravel framework ... I'm sure the answer is in there somewhere. Also try to dump the $response object and see what it looks like, there should be some clues there.
The first thing I would try, though, is accessing your data through the $response object.
You can access data in the response and it can be checked..
public function testSimpleLastProducts() {
$res = $this->call('GET', '/');
$this->assertResponseOk();
$this->assertViewHas('lastProducts');
$lastProductOnView = $res->original['lastProducts'];
$this->assertEquals(6, count($lastProductOnView));
}
This worked for me:
$response->getSession()->get("errors")
And from there you can check the contents of the message box for whatever error you might want verify.
Looking for something like this in 2019, I got:
$response->getData()->data[0]->...my properties
Still looking for a simpler way to access it.
I have had the same problem, but my case was a bit special, because I push my data to view via view()->share($params); and in such cases the solution: $content = $response->getOriginalContent()->getData(); does not give the data out.
and I could not use $response->assertViewHas(...) because my data was objects (models) and I needed to verify object properties (keys and id-s).
So my solution was
$data = $response->original->gatherData();
$this->assertSame($currency->key, $data['currency']->key);
Tested on Laravel 8

Categories