Generating many URLs in one Symfony Controller action - php

I have an action with annotation route to "showAllLinks"
/**
* #param Request $request
* #return Response
* #Route("/showAllLinks/")
**/
When that action is accessed, I would like to generate URLs (relative and absolute) to several other actions I wrote in the same controller. Is that possible? So far I've tried with several URLs being generated and pushed into array which then would be included in response, but from what I see, Symfony is either asking for
The Response content must be a string or object implementing __toString(), "array" given.
See action below:
public function showAllLinksAction(Request $request)
{
$linksArr = [];
$url1 = $this->generateUrl('helloWorld', [], 302, UrlGeneratorInterface::ABSOLUTE_URL);
$linksArr[] = $url1;
$url2 = $this->generateUrl('goodbye', [], 302, UrlGeneratorInterface::ABSOLUTE_URL);
$linksArr[] = $url2;
$url3 = $this->generateUrl('welcome', [], 302, UrlGeneratorInterface::ABSOLUTE_URL);
$linksArr[] = $url3;
$url4 = $this->generateUrl('welcome', [], 302, UrlGeneratorInterface::ABSOLUTE_URL);
$linksArr[] = $url4;
return new Response($linksArr);
}

You are passing an array to the response object, you should pass an string. These are the params of the response object, from the Symfony documentation:
use Symfony\Component\HttpFoundation\Response;
$response = new Response(
'Content',
Response::HTTP_OK,
array('content-type' => 'text/html')
);
I think you could use
$response = new Response('Content');
or
$response = new Response();
$response->setContent('Content');
In any case, the 'Content' parameter is a string. You are trying to set $linksArr, which is an array. This should work, although may not be the result you want to achieve:
return new Response(implode(",", $linksArr));

Related

PHPUnit and mock request from Guzzle

I have a class with the following function :
public function get(string $uri) : stdClass
{
$this->client = new Client;
$response = $this->client->request(
'GET',
$uri,
$this->headers
);
return json_decode($response->getBody());
}
How can I mock the request method from PHPUnit? I tried different ways but it always tries to connect to the uri specified.
I tried with :
$clientMock = $this->getMockBuilder('GuzzleHttp\Client')
->setMethods('request')
->getMock();
$clientMock->expects($this->once())
->method('request')
->willReturn('{}');
But this didn't work. What can I do? I just need to mock the response to be empty.
Thanks
PD : Client comes from (use GuzzleHttp\Client)
I think as suggested is better to use http://docs.guzzlephp.org/en/stable/testing.html#mock-handler
as it looks like the most elegant way to do it properly.
Thank you all
The mocked Response doesn't need to be anything in particular, your code just expects it to be an object with a getBody method. So you can just use a stdClass, with a getBody method which returns some json_encoded object. Something like:
$jsonObject = json_encode(['foo']);
$uri = 'path/to/foo/bar/';
$mockResponse = $this->getMockBuilder(\stdClass::class)->getMock();
$mockResponse->method('getBody')->willReturn($jsonObject);
$clientMock = $this->getMockBuilder('GuzzleHttp\Client')->getMock();
$clientMock->expects($this->once())
->method('request')
->with(
'GET',
$uri,
$this->anything()
)
->willReturn($mockResponse);
$result = $yourClass->get($uri);
$expected = json_decode($jsonObject);
$this->assertSame($expected, $result);
I prefer this way to mock a Client in PHP. In this example I am using Guzzle Client.
Clone the code or install it via composer
$ composer require doppiogancio/mocked-client
And then...
$builder = new HandlerStackBuilder();
// Add a route with a response via callback
$builder->addRoute(
'GET', '/country/IT', static function (ServerRequestInterface $request): Response {
return new Response(200, [], '{"id":"+39","code":"IT","name":"Italy"}');
}
);
// Add a route with a response in a text file
$builder->addRouteWithFile('GET', '/country/IT/json', __DIR__ . '/fixtures/country.json');
// Add a route with a response in a string
$builder->addRouteWithFile('GET', '{"id":"+39","code":"IT","name":"Italy"}');
// Add a route mocking directly the response
$builder->addRouteWithResponse('GET', '/admin/dashboard', new Response(401));
$client = new Client(['handler' => $builder->build()]);
Once you have mocked the client you can use it like this:
$response = $client->request('GET', '/country/DE/json');
$body = (string) $response->getBody();
$country = json_decode($body, true);
print_r($country);
// will return
Array
(
[id] => +49
[code] => DE
[name] => Germany
)
In addition to the current answer about using MockHandler, it's possible to process the request so that you can validate the calls.
The following example passes a callable which just tests the request method and throws an exception if not POST, if that is OK it returns the response. The principle can be expanded to test other details about the request...
$mock = new MockHandler([
function ($request) {
$this->assertEquals('POST', $request->getMethod());
return new Response(
200,
[],
json_encode([ "access_token" => '1234e' ])
);
},
new Response(
200,
[],
json_encode([ "details" =>
[
[
"orderID" => 229783,
],
[
"orderID" => 416270,
],
],
])
),
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
So the first call to the client has the test included, the second call just returns a response.
Just noticed that any time you use a callable to process the request, you MUST return a Response object if you expect the process to continue.

Guzzle: Access uploaded files in history middleware

I'm trying to access an uploaded file in the history middleware for Guzzle (v6).
My actual code receives a request (so is using the ServerRequestInterface), then uses Guzzle to send the request elsewhere.
I'm trying to test uploaded files going through this layer, but I can't seem to access them in the Request object returned by Guzzle's middleware.
Example code:
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\ServerRequest;
use GuzzleHttp\Psr7\UploadedFile;
class DoNotCommitTest extends \PHPUnit\Framework\TestCase
{
public function testUploads()
{
$request = new ServerRequest('GET', 'http://example.com/bla');
$file = new UploadedFile('test', 100, \UPLOAD_ERR_OK);
$request = $request->withUploadedFiles([$file]);
$this->assertCount(1, $request->getUploadedFiles());
// Mock Guzzle request, assert on the request it 'sent'
$mock = new MockHandler([
function (ServerRequest $request, array $options) {
// This fails...
$this->assertCount(1, $request->getUploadedFiles());
}
]);
$historyContainer = [];
$history = Middleware::history($historyContainer);
$handler = HandlerStack::create($mock);
$handler->push($history);
$client = new Client(['handler' => $handler]);
$client->send($request);
}
}
If you follow execution chain, $client->send($request) at some point calls private applyOptions function, which calls Psr7\modify_request function. If you look at Psr7\modify_request function:
...
if ($request instanceof ServerRequestInterface) {
return new ServerRequest(
isset($changes['method']) ? $changes['method'] : $request->getMethod(),
$uri,
$headers,
isset($changes['body']) ? $changes['body'] : $request->getBody(),
isset($changes['version'])
? $changes['version']
: $request->getProtocolVersion(),
$request->getServerParams()
);
}
...
It returns new ServerRequest object without preserving your uploaded files array (ServerRequest object doesn't have the uploadedFiles as an argument in the constructor). That's why you lost your uploadedFiles array.
UPDATE:
I created an issue and a pull request to fix it.

How to modify the URI before redirect

How do I modify the URI instance GuzzleHttp\Psr7\Uri before a redirect, especially the query part?
I have tried to add a middleware that modifies the request, but once I add a CurlHandler the response body always returns an empty string.
$stack = new HandlerStack();
$stack->setHandler(new CurlHandler());
$client = new Client(['handler' => $stack]);
$stack->push(Middleware::mapRequest(function (RequestInterface $request) {
// Modify request
return $request;
}));
The problem was due to the HandlerStack type, the CurlHandler does not handle redirects by default.
How to modify the request URI
$stack = HandlerStack::create();
$stack->push(Middleware::mapRequest(function (RequestInterface $request) {
// Retrieve the URI
$uri = $request->getUri();
$query = // update query
// Update the URI query
$uri = $uri->withQuery($query);
return $request->withUri($uri);
}));

Symfony2: UnitTests for AJAX Controllers

I'm going to write some Symfony2 UnitTests (derived from Symfony\ Bundle\ FrameworkBundle\ Test\ WebTestCase) to test ajax controllers, similar to this How to get Ajax post request by symfony2 Controller.
My big problem is to get the parameters into the "request" bag of the request, not into the "parameter" bag. Similar to the upper example the method in the controller looks like this:
public function ajaxAction(Request $request)
{
$data = $request->request->get('data');
}
But if i do a var_dump of the $request, the paramaters i supply in the WebTestCase do not appear in $request->request, but in $request->parameter. Let's say this is the portion of code in my webtestcase:
....
$client = static::createClient();
$client->request('POST', '/ajax/blahblah', ... ?????);
I already tried supplying the parameter(s) directly within the url as
/ajax/blahblah?data=whocares
I tried specifying the parameter within an array
$client->request('POST', '/ajax/blahblah', array('data' => 'fruityloops'));
But nothing worked. Any chance to get this running?
Thanks in advance
Hennes
After you make the request, you need to get the response. Try this:
$client = static::createClient();
$client->request('POST', '/ajax/blahblah', array('data' => 'fruityloops'));
$response = $client->getResponse();
$this->assertEquals(200, $response->getStatusCode());
//convert to array
$data = json_decode($response->getContent(true), true);
var_dump($data);
$this->assertArrayHasKey('your_key', $data);

Access Guzzle Response from Goutte

I'm trying to access to the Guzzle Response object from Goutte. Because that object has nice methods that i want to use. getEffectiveUrl for example.
As far as i can see there is no way doing it without hacking the code.
Or without accessing the response object, is there a way to get the last redirected url froum goutte?
A little late, but:
If you are only interested in getting the URL you were last redirected to, you could simply do
$client = new Goutte\Client();
$crawler = $client->request('GET', 'http://www.example.com');
$url = $client->getHistory()->current()->getUri();
EDIT:
But, extending Goutte to serve your needs is fairly easy. All you need is to override the createResponse() method and store the GuzzleResponse
namespace Your\Name\Space;
class Client extends \Goutte\Client
{
protected $guzzleResponse;
protected function createResponse(\Guzzle\Http\Message\Response $response)
{
$this->guzzleResponse = $response;
return parent::createResponse($response);
}
/**
* #return \Guzzle\Http\Message\Response
*/
public function getGuzzleResponse()
{
return $this->guzzleResponse;
}
}
Then you can access the response object as desired
$client = new Your\Name\Space\Client();
$crawler = $client->request('GET', 'http://localhost/redirect');
$response = $client->getGuzzleResponse();
echo $response->getEffectiveUrl();

Categories