Symfony: How to render a template as a simple txt - php

I have to render a template of an action as a simple .txt file.
How can I do this? Is there a way other than using the Response object?
Using a Response object:
$content = $this->get('templating')->render(
'AppBundle:Company:accountBillingInvoice.txt.twig',
[
'invoice' => 'This is the invoice'
]
);
$response = new Response($content , 200);
$response->headers->set('Content-Type', 'text/plain');

I can't see what's wrong with using the Response object - it's pretty simple!
If you want to render text responses from many controller actions and you don't want to repeat yourself a lot, you can define some service class that builds up the response for you, like:
class TextResponseRenderer
{
/** #var EngineInterface */
private $engine;
// constructor...
/**
* #param string $template The name of the twig template to be rendered.
* #param array $parameters The view parameters for the template.
* #return Response The text response object with the content and headers set.
*/
public function renderResponse(string $template, array $parameters): Response
{
$content = $this->engine->render($template, $parameters);
$textResponse = new Response($content , 200);
$textResponse->headers->set('Content-Type', 'text/plain');
return $textResponse;
}
}
Other option may be writing a listener for the kernel.response that modifies the response headers, but this might be over-complicating things. See more info here.

Related

Symfony Serializer: Deserializing Json to Entity

I am trying to use Symfony's Serializer to deserialize a Json to my entity "DossierDTO".
class DossierDTO
{
#[Groups(['test'])]
public string $idActeurCreateur;
#[Groups(['test'])]
public string $idDossierVise;
#[Groups(['test'])]
public string $idProjet;
public ArrayCollection $personnes;
public ArrayCollection $terrains;
.
.
.
more fields
I would like to deserialize only the fields tagged with the #[Groups(['test'])] annotations.
Here is my call to fetch the json object and my attempt to deserialize it:
/**
* Make a request to API
* #param string $method: request method (POST, GET...)
* #param string $suffix: URI suffix (/example)
* #param array $body: request body
* #throws Exception
* #return ResponseInterface
*/
public function myRequest(string $method, string $suffix, ?array $body): ResponseInterface
{
$jsonContent = is_null($body) ? json_encode(new stdClass) : $this->serializer->serialize($body, 'json');
try {
$response = $this->client->request($method, $this->infos["uri"] . $suffix, [
'headers' => $this->infos["headers"],
'body' => $jsonContent
]);
} catch (Exception $e) {
$this->logger->error($e->getMessage());
}
$dossier = $this->serializer->deserialize($response->getContent(), DossierDTO::class, 'json', ["groups" => "test"]);
dd($dossier, $response->getContent());
}
And this is what my dump shows:
So basically, I don't get the fields that I would like to, even when I remove the "#[Groups(['test'])]" the result is the same.
It always shows me the two ArrayCollection fields (empty) and only these...
I'm working with Symfony 5.2.9
From your screenshot I can see that response JSON has nested object, keyed by "projet". Looks like you are mapping incorrect structure. Try this:
$this->serializer->deserialize($response->getContent()['projet'], DossierDTO::class, 'json', ["groups" => "test"]);

Testing Stripe in Laravel

I'm creating a subscription-based SaaS platform in Laravel, where Laravel Cashier does not suit my needs. Therefore I need to implement the subscription-engine myself using the Stripe library.
I found it easy to implement the connection between Laravel and Stripe via hooking into the creation and deletion events of a Subscription class, and then create or cancel a Stripe subscription accordingly.
The Stripe library is unfortunately largely based on calling static methods on some predefined classes (.. like \Stripe\Charge::create()).
This makes it hard for me to test, as you normally would allow dependency injection of some custom client for mocking, but since the Stripe library is referenced statically, there is no client to inject. Is there any way of creating a Stripe client class or such, that I can mock?
Hello from the future!
I was just digging into this. All those classes extend from Stripe's ApiResource class, keep digging and you'll discover that when the library is about to make an HTTP request it calls $this->httpClient(). The httpClient method returns a static reference to a variable called $_httpClient. Conveniently, there is also a static method on the Stripe ApiRequestor class called setHttpClient which accepts an object which is supposed to implement the Stripe HttpClient\ClientInterface (this interface only describes a single method called request).
Soooooo, in your test you can make a call to ApiRequestor::setHttpClient passing it an instance of your own http client mock. Then whenever Stripe makes an HTTP request it will use your mock instead of its default CurlClient. Your responsibility is then have your mock return well-formed Stripe-esque responses and your application will be none the wiser.
Here is a very dumb fake that I've started using in my tests:
<?php
namespace Tests\Doubles;
use Stripe\HttpClient\ClientInterface;
class StripeHttpClientFake implements ClientInterface
{
private $response;
private $responseCode;
private $headers;
public function __construct($response, $code = 200, $headers = [])
{
$this->setResponse($response);
$this->setResponseCode($code);
$this->setHeaders($headers);
}
/**
* #param string $method The HTTP method being used
* #param string $absUrl The URL being requested, including domain and protocol
* #param array $headers Headers to be used in the request (full strings, not KV pairs)
* #param array $params KV pairs for parameters. Can be nested for arrays and hashes
* #param boolean $hasFile Whether or not $params references a file (via an # prefix or
* CURLFile)
*
* #return array An array whose first element is raw request body, second
* element is HTTP status code and third array of HTTP headers.
* #throws \Stripe\Exception\UnexpectedValueException
* #throws \Stripe\Exception\ApiConnectionException
*/
public function request($method, $absUrl, $headers, $params, $hasFile)
{
return [$this->response, $this->responseCode, $this->headers];
}
public function setResponseCode($code)
{
$this->responseCode = $code;
return $this;
}
public function setHeaders($headers)
{
$this->headers = $headers;
return $this;
}
public function setResponse($response)
{
$this->response = file_get_contents(base_path("tests/fixtures/stripe/{$response}.json"));
return $this;
}
}
Hope this helps :)
Based off Colin's answer, here is an example that uses a mocked interface to test creating a subscription in Laravel 8.x.
/**
* #test
*/
public function it_subscribes_to_an_initial_plan()
{
$client = \Mockery::mock(ClientInterface::class);
$paymentMethodId = Str::random();
/**
* Creates initial customer...
*/
$customerId = 'somecustomerstripeid';
$client->shouldReceive('request')
->withArgs(function ($method, $path, $params, $opts) use ($paymentMethodId) {
return $path === "https://api.stripe.com/v1/customers";
})->andReturn([
"{\"id\": \"{$customerId}\" }", 200, []
]);
/**
* Retrieves customer
*/
$client->shouldReceive('request')
->withArgs(function ($method, $path, $params) use ($customerId) {
return $path === "https://api.stripe.com/v1/customers/{$customerId}";
})->andReturn([
"{\"id\": \"{$customerId}\", \"invoice_settings\": {\"default_payment_method\": \"{$paymentMethodId}\"}}", 200, [],
]);
/**
* Set payment method
*/
$client->shouldReceive('request')
->withArgs(function ($method, $path, $params) use ($paymentMethodId) {
return $path === "https://api.stripe.com/v1/payment_methods/{$paymentMethodId}";
})->andReturn([
"{\"id\": \"$paymentMethodId\"}", 200, [],
]);
$subscriptionId = Str::random();
$itemId = Str::random();
$productId = Str::random();
$planName = Plan::PROFESSIONAL;
$plan = Plan::withName($planName);
/**
* Subscription request
*/
$client->shouldReceive('request')
->withArgs(function ($method, $path, $params, $opts) use ($paymentMethodId, $plan) {
$isSubscriptions = $path === "https://api.stripe.com/v1/subscriptions";
$isBasicPrice = $opts["items"][0]["price"] === $plan->stripe_price_id;
return $isSubscriptions && $isBasicPrice;
})->andReturn([
"{
\"object\": \"subscription\",
\"id\": \"{$subscriptionId}\",
\"status\": \"active\",
\"items\": {
\"object\": \"list\",
\"data\": [
{
\"id\": \"{$itemId}\",
\"price\": {
\"object\": \"price\",
\"id\": \"{$plan->stripe_price_id}\",
\"product\": \"{$productId}\"
},
\"quantity\": 1
}
]
}
}", 200, [],
]);
ApiRequestor::setHttpClient($client);
$this->authenticate($this->user);
$res = $this->putJson('/subscribe', [
'plan' => $planName,
'payment_method_id' => $paymentMethodId,
]);
$res->assertSuccessful();
// Actually interesting assertions go here
}

Behat reusable actions add header

I'm using Behat in a Symfony2 app.
I have made a reusable action to add a HTTP header on some scenarios
/**
* #Given I am authenticated as admin
*/
public function iAmAuthenticatedAsAdmin()
{
$value = 'Bearer xxxxxxxxxxx';
$response = new Response();
$response->headers->set('Authorization', $value);
$response->send();
return $response;
}
This action is call when I add the I am authenticated as admin step in my scenario but it doesn't add my header. Like this
Scenario: I find all my DNS zones
Given I am authenticated as admin
And I send a GET request to "/api/dns"
Then the response code should be 200
How can I add a HTTP header before my request step in my scenario, using a reusable action ?
Is it possible ?
Thank.
I have find the way to do this.
If you are using WebApiExtension
Just import the WebApiContext in your context class like this
/**
* #param BeforeScenarioScope $scope
*
* #BeforeScenario
*/
public function gatherContexts(BeforeScenarioScope $scope)
{
$environment = $scope->getEnvironment();
$this->webApiContext = $environment->getContext('Behat\WebApiExtension\Context\WebApiContext');
}
And you just have now to use the iSetHeaderWithValue :
/**
* #Given I am authenticated as admin
*/
public function iAmAuthenticatedAsAdmin()
{
$name = 'Authorization';
$value = 'Bearer xxxxxxxx';
$this->webApiContext->iSetHeaderWithValue($name, $value);
}
Why don't you simply store it in session (hint: session should be destroyed at every scenario; take a look to BeforeScenario) and use whenever you need it?
Because I guess it's added but, on next request, it's gone as a brand new pair of request/response are generated (if I understand your needs correctly)

Adding GET input to my resourceful controller in Laravel

Using Laravel 4 to create a "Read-it-Later" application just for testing purposes.
I'm able to successfully store a URL and Description into my application using the following curl command:
curl -d 'url=http://testsite.com&description=For Testing' readitlater.local/api/v1/url
I'm interested in using GET to accomplish the same thing but by passing my variables in a URL (e.g. readitlater.local/api/v1/url?url=testsite.com?description=For%20Testing)
Here is my UrlController segment:
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store()
{
$url = new Url;
$url->url = Request::get('url');
$url->description = Request::get('description');
$url->save();
return Response::json(array(
'error' => false,
'urls' => $urls->toArray()),
200
);
}
Here is my Url model:
<?php
class Url extends Eloquent {
protected $table = 'urls';
}
I read through the Laravel docs on input types but I'm not certain how to apply that to my current controller: http://laravel.com/docs/requests#basic-input
Any tips?
You didn't apply what you correctly linked to...Use Input::get() to fetch anything from GET or POST, and the Request class to get info on the current request. Are you looking for something like this?
public function store()
{
$url = new Url; // I guess this is your Model
$url->url = Request::url();
$url->description = Input::get('description');
$url->save();
return Response::json(array(
'error' => false,
'urls' => Url::find($url->id)->toArray(),
/* Not sure about this. You want info for the current url?
(you already have them...no need to query the DB) or you want ALL the urls?
In this case, use Url::all()->toArray()
*/
200
);
}

Symfony2 - PdfBundle not working

Using Symfony2 and PdfBundle to generate dynamically PDF files, I don't get to generate the files indeed.
Following documentation instructions, I have set up all the bundle thing:
autoload.php:
'Ps' => __DIR__.'/../vendor/bundles',
'PHPPdf' => __DIR__.'/../vendor/PHPPdf/lib',
'Imagine' => array(__DIR__.'/../vendor/PHPPdf/lib', __DIR__.'/../vendor/PHPPdf/lib/vendor/Imagine/lib'),
'Zend' => __DIR__.'/../vendor/PHPPdf/lib/vendor/Zend/library',
'ZendPdf' => __DIR__.'/../vendor/PHPPdf/lib/vendor/ZendPdf/library',
AppKernel.php:
...
new Ps\PdfBundle\PsPdfBundle(),
...
I guess all the setting up is correctly configured, as I am not getting any "library not found" nor anything on that way...
So, after all that, I am doing this in the controller:
...
use Ps\PdfBundle\Annotation\Pdf;
...
/**
* #Pdf()
* #Route ("/pdf", name="_pdf")
* #Template()
*/
public function generateInvoicePDFAction($name = 'Pedro')
{
return $this->render('AcmeStoreBundle:Shop:generateInvoice.pdf.twig', array(
'name' => $name,
));
}
And having this twig file:
<pdf>
<dynamic-page>
Hello {{ name }}!
</dynamic-page>
</pdf>
Well. Somehow, what I just get in my page is just the normal html generated as if it was a normal Response rendering.
The Pdf() annotation is supposed to give the "special" behavior of creating the PDF file instead of rendering normal HTML.
So, having the above code, when I request the route http://www.mysite.com/*...*/pdf, all what I get is the following HTML rendered:
<pdf>
<dynamic-page>
Hello Pedro!
</dynamic-page>
</pdf>
(so a blank HTML page with just the words Hello Pedro! on it.
Any clue? Am I doing anything wrong? Is it mandatory to have the alternative *.html.twig apart from the *.pdf.twig version? I don't think so... :(
Ok I got it.
For some reason, the example that comes in the bundle documentation didn't work for me. Nevertheless, there is this class in de bundle: http://github.com/psliwa/PdfBundle/blob/master/Controller/ExampleController.php, where I could find an example that did work for me. This is the code that I finally used:
/**
* #Route ("/generateInvoice", name="_generate_invoice")
*/
public function generateInvoiceAction($name = 'Pedro')
{
$facade = $this->get('ps_pdf.facade');
$response = new Response();
$this->render('AcmeStoreBundle:Shop:generateInvoiceAction.pdf.twig', array("name" => $name), $response);
$xml = $response->getContent();
$content = $facade->render($xml);
return new Response($content, 200, array('content-type' => 'application/pdf'));
}
Next challenge: store that PDF into disk.
It's because you've missed the "_format" option in the URL.
$this->render() shouldn't be used with the #Template annotation. The #Template will serve the correct template's format depending of the _format parameter.
...
use Ps\PdfBundle\Annotation\Pdf;
...
/**
* #Pdf()
* #Route ("/pdf.{_format}", name="_pdf")
* #Template()
*/
public function generateInvoicePDFAction($name = 'Pedro')
{
return array('name' => $name);
}
Should work fine.

Categories