getUrl dropping _format when set to json - symfony

I'm trying to generate a url and set the _format variable to json. the .json part is never added to the route. setting to html or xml appends the format correctly.
The route from app/console router:debug,
api_1_get_page GET ANY ANY /api/page/{id}.{_format}
And my functional test code,
$this->getUrl('api_1_get_page', array('id' => $page->getId(), '_format' => 'json'));
when I dump this I get,
string(18) "/api/pages/1"
Whereas,
$this->getUrl('api_1_get_page', array('id' => $page->getId(), '_format' => 'html'));
returns string(18) "/api/pages/1.html"
I'm guessing there is a setting controlling this, I've tried setting every fos_rest config setting on and off and nothing is making a difference. This is also causing me issues when tests are checking the Location: header expecting .json.

Have you set .json as the default format in your route?
If you're using FOSRestBundle look at routing_loader in the FOSRestBundle config
See: https://github.com/FriendsOfSymfony/FOSRestBundle/blob/master/Resources/doc/5-automatic-route-generation_single-restful-controller.md#routing

Related

Altering a dynamic route in Drupal 8 with RouteSubscriber

I have a web-app made in Drupal 8, and I have some routing problems. Users will login using an external service, and after login they are sent to the path /user/{user}, where {user} is their user id. I want to change this behaviour and send them to a page /dashboard. Not having access to what the external service routing is doing, I need to reroute /user/{user} to /dashboard. It seems like the drupal 8 redirect module could solve the problem, but I am not too keen on using an external module just for this simple task.
Because of this, I tried to change the route with drupal's RouteSubscriber and alterRoutes method. So I made my RouteSubscriber class in my module called "module" like this:
class RouteSubscriber extends RouteSubscriberBase {
protected function alterRoutes(RouteCollection $collection) {
if ($route = $collection->get('user.page')) {
$route->setDefaults(array(
'_controller' => '\Drupal\module\Controller\Dashboard::content',
));
}
if ($route = $collection->get('entity.user.canonical')) {
$route->setDefaults(array(
'_controller' => '\Drupal\module\Controller\Dashboard::content',
));
}
}
}
The route user.page has path /user, and the path entity.user.canonical is the route I'm interested in, which has path /user/{user}, where {user} is again a path parameter. When I go to the page /user, the dashboard is displayed as expected, but going to for example /user/123 does not show the dashboard, but seems display what the route originally did. Just to test whether it was impossible to alter this route, I tried to set all routes to have display the dashboard by inserting the following code into RouteSubscriber:
foreach ($collection->all() as $route) {
$route->setDefaults(array(
'_controller' => '\Drupal\module\Controller\Dashboard::content',
'pid' => '',
'uid' => '',
'modifier' => '',
'display' => '',
));
}
The junk pid, uid, modifier and display are just defaults to different routes path parameters so the code will run. This makes the page /user/123 correctly display the dashboard! However, I do get the following error message at the bottom of the screen:
The website encountered an unexpected error. Please try again later.
Symfony\Component\Routing\Exception\MissingMandatoryParametersException: Some mandatory parameters are missing ("filter") to generate a URL for route "devel.configs_list". in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 182 of core/lib/Drupal/Core/Routing/UrlGenerator.php).
So, what am I doing wrong here? Can what I want be done, or should I do something different to achieve what I want? Also, feel free to ask for more code if needed!

Drupal 8, http request to server and append to the site

I have a Drupal 8 site and I need to make a http request to another server (for content) and append it into the page like footer. I can't do this after DOM is loaded because of SEO issues.
I'm familiar with WordPress and so easy to do it with WP. However, I'm confused about how to do this with .twig, Drupal 8. Any suggestions would be great. Thanks.
If you want the content to be part of the DOM when it is sent to the browser this is not something you want to do in Twig, you should have the content loaded earlier in the process.
You can create a module that defines custom block and place that block in the correct region of your theme.
The block plugin class requires you to write a build() method that returns a render array for your block. Within build() you can do whatever you need to acquire the content, including making an HTTP Request using Symfony's Guzzle client:
public function build() {
$url = 'https://www.example.com/remote/service';
$client = \Drupal::httpClient();
$request = $client->createRequest('GET', $url);
// Do whatever's needed to extract the data you need from the request...
$build = ['my_remote_block' => [
'#theme' => 'my_custom_theme_function',
'#attributes' => [
//An array of variables to pass to the theme
],
'#cache' => [
//Some appropriate cache settings
],
],
];
If you are getting HTML back from your request you could skip the custom theme function and return an array with '#type' => 'markup' and then a field for the markup. The rest of this example assumes you get data back and want to render it yourself.
In your module's .module file you can define the custom theme function (so you can use a twig file of your own design).
function my_module_theme($existing, $type, $theme, $path) {
return [
'my_custom_theme_function' => [
'variables'=> [
// defaults for variables used in this block.
],
],
];
}
Then finally you can create a twig file named my-custom-theme-function.html.twig to render the output.
Often these kinds of setups are quite slow (since the browser's request then triggers another HTTP request + processing time) so you should consider either caching the block as much as possible or using a technique like BigPipe (which is probably not an option for you based on your question but seemed worth pointing out).

HTTP client Cakephp 3 ignores json body

I'm currently writing a RESTful API in Cakephp 3 whereby I need to test a POST operation through http://host.com/api/pictures. The code for the test:
<?php
namespace App\Test\TestCase\Controller;
use App\Controller\Api\UsersController;
use Cake\TestSuite\IntegrationTestCase;
use Cake\Network\Http\Client;
use Cake\Network\Http\FormData;
class ApiPicturesControllerTest extends IntegrationTestCase{
public $fixtures = [
'app.users',
'app.comments',
'app.albums',
'app.users_albums'
];
public function testAdd(){
// $data = new FormData();
$accessToken ='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjksImV4cCI6MTQ1NzYyOTU3NH0.NnjXWEQCno3PUiwHhnUCBjiknR-NlmT42oPLA5KhuYo';
$http = new Client([
'headers' => ['Authorization' => 'Bearer ' . $accessToken, 'Content-Type' => 'application/json']
]);
$data = [
"album_id" => 1,
"link" => "http://www.google.com",
"description" => "testtesttest",
"favorite" => true
];
$result = $http->post('http://vecto.app/api/pictures/add.json', $data, ['type'=>'json']);
// $this->assertResponseOk();
// debug($result);
}
}
When I try to debug the result I get a 'cannot add or update child row' while I'm sure the responding id does exists
(the fixtures does have the id's too). Additionally, the log indicates that it only tries to insert the create/update rows. Therefore, I'm pretty sure the data is ignored but however I can't find a solution. I already tried different combination of headers like only application/json for Accept, application/json for Content-Type etc. I'm using the CRUD plugin for Cakephp to pass the data to an add function.
Postman output
Furthermore, I tried the Postman Chrome plugin to save the data and that actually does work. Does anyone know what I'm doing wrong in the test?
That's not how the integration test case is ment to be used. You are dispatching an external, real request, which will leave the test environment, while you should use the request dispatching tools that the integration test case supplies, that is
IntegrationTestCase::get()
IntegrationTestCase::post()
IntegrationTestCase::put()
etc...
These methods will dispatch simulated requests that do not leave the test environment, which is crucial for things to work properly, as you want to use test connections, inspect possible exceptions, have access to the used session, etc...
ie, you should do something along the lines of
$accessToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjksImV4cCI6MTQ1NzYyOTU3NH0.NnjXWEQCno3PUiwHhnUCBjiknR-NlmT42oPLA5KhuYo';
$this->configRequest([
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
'Content-Type' => 'application/json'
]
]);
$data = [
"album_id" => 1,
"link" => "http://www.google.com",
"description" => "testtesttest",
"favorite" => true
];
$this->post('/api/pictures/add.json', json_encode($data));
Note that a content type of application/json will require you to send raw JSON data! If you don't actually need/want to test parsing of raw input, then you could skip that header, and pass the array as data instead.
See also
Cookbook > Testing > Controller Integration Testing
API > \Cake\TestSuite\IntegrationTestCase

Silverstripe 3 Extending Error Mail with HTTP_X_FORWARDED_FOR

By default the error mail only takes these server variables from log.php:
protected static $log_globals = array(
'_SERVER' => array(
'HTTP_ACCEPT',
'HTTP_ACCEPT_CHARSET',
'HTTP_ACCEPT_ENCODING',
'HTTP_ACCEPT_LANGUAGE',
'HTTP_REFERRER',
'HTTP_USER_AGENT',
'HTTPS',
'REMOTE_ADDR',
),
);
How do I add 'HTTP_X_FORWARDED_FOR' to my error e-mails without modifying the core files?
This is actually possible via the new configuration system in Silverstripe. Have a YAML config file with the following:
SS_Log:
log_globals:
'_SERVER':
- 'HTTP_X_FORWARDED_FOR'
This adds HTTP_X_FORWARDED_FOR to the _SERVER array on the log_globals static variable.

Silex translation stops working when validation is registered

I registered TranslationServiceProvider with YAML and it works fine:
$app->register(new TranslationServiceProvider(), array(
'locale_fallback' => 'ru',
));
$app['translator'] = $app->share($app->extend('translator', function($translator, $app) {
$translator->addLoader('yaml', new YamlFileLoader());
$translator->addResource('yaml', CONTENT_PATH . '/locales/en.yml', 'en');
return $translator;
}));
Then I register ValidationServiceProvider like this:
$app->register(new Silex\Provider\ValidatorServiceProvider());
When I register Validation, Translation stops working. All strings that should be translated don't get translated with no errors.
I am changing locale in the countroller, so it might have something to do with it:
$en->get('/{slug}', function (Silex\Application $app, $slug) {
$app['locale'] = 'en';
So, all services (from service providers) get locales in runtime, before request, in create process. And, if real locale not presented, translator gets the fallback locale.
Problem: you can not change locale in runtime, because locale is a variable and not presented as link (if locale will be object, you can change locale).
Solution: Change/Set locale on request subscriber.

Resources