Is there a generic URL builder/manipulator in Drupal/Symfony? - symfony

How to build and/or modify generic URLs on Drupal/Symfony?
For example, having at the input an URL like: http://some.url/with?param1=value1#fragment I would like to be able to manipulate any parts of the url including:
cut off the query (search) part
add more query parameters
change the path part
replace domain
add/change fragment
etc
I couldn't find anything appropriate in Drupal or Symfony.

In Drupal 8 there is lib/Drupal/Core/Url.php class which provide some methods to get/set parameters
Availables methods to setting the route parameters:
setRouteParameters($parameters)
setRouteParameter($key, $value)
Availables methods to set the route options:
setOptions($options)
setOption($name, $value)
How to use the setParameter method:
/**
* #var Drupal\Core\Url $url
*/
$url->setRouteParameter('arg_0', $arg0);

With Symfony 3.3 you can use Request::create($url):
use Symfony\Component\HttpFoundation\Request;
$url = 'http://some.url/with?param1=value1#fragment'
$req = Request::create($url)
Then you can call
$req->getQueryString() // param1=value1
$req->getHost() // some.url
$req->getSchemeAndHttpHost() // http://some.url
$req->getBaseUrl() // /with
Ref http://api.symfony.com/3.3/Symfony/Component/HttpFoundation/Request.html
I don't think this class provides any setter for host/param/path so you can do the following:
str_replace($req->getHost(), 'new-host.com', $url) // change host
About the hash fragment #fragment, it doesn't seem to be available on server-side (see Get fragment (value after hash '#') from a URL in php).

Related

API to get a content type against URL

I have a scenario where I need to perform some redirection of a not found url
http://localhost/drupal9/node/1/search
the word search is added though a plugin I am using and it is a front-end route not a backend so upon refreshing this url I get Not Found which totally makes sense what I need to do is remove the word search from the URL and redirect to,
http://localhost/drupal9/node/1/
as search is a common word and can be used in other content type I first need to check whether the URL is of my custom content type. let me show you a piece of implementation I already have.
function [module]_preprocess_page(&$variables) {
$query = \Drupal::entityQuery('node')
->condition('type', [module]);
$nids = $query->execute();
if(array_search(2,$nids)){
echo "yes";
}
}
so over here what I am doing is grabbing all the nodes with my content type and grabbing the Nid from URI and matching them and this does work but there is another problem with this.
In the page properties we have an option of ALias so if the user uses a custom alias, then I dont get the Nid in the URI anymore so this logic breaks,
the question may seem a bit tricky but the requirement is simple.I am looking for a unified solution to parse the URL into some drupal API and simply getting back the content type name.The Url may contain a custom alias or a Nid
You can create an EventSubscriber subscribing the event kernel.request to handle the case of URL <node URL>/search.
For detailed steps to create an EventSubscriber, you can see here.
And below is what you need to put in your EventSubscriber class:
RequestSubscriber.php
<?php
namespace Drupal\test\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Class RequestSubscriber.
*/
class RequestSubscriber implements EventSubscriberInterface {
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents() {
return [
KernelEvents::REQUEST => 'onKernelRequest',
];
}
public function onKernelRequest($event) {
$uri = $event->getRequest()->getRequestUri(); // get URI
if (preg_match('/(.*)\/search$/', $uri, $matches)) { // check if URI has form '<something>/search'
$alias = $matches[1];
$path = \Drupal::service('path_alias.manager')->getPathByAlias($alias); // try to get URL from alias '<something>'
if (preg_match('/node\/(\d+)/', $path, $matches)) { // if it is a node URL
$node = \Drupal\node\Entity\Node::load($matches[1]);
$content_type = $node->getType();
//... some logic you need
}
}
}
}

$request->request->replace() what does it do?

I was going through some code in symfony, and I found
$request->request->replace()
Actually, a form is posted and its value is fetched in a function say,
public function someFunction(Request $request){
$data = $request->request->all() ? : json_decode($request->getContent(), true);
$request->request->replace($data);
}
When I dumped,
$request->request->replace($data)
The result is null. I didn't understand why is it used and what are its benefits?
I searched about it, some say it is used to sanitise the data, some say we should not use it as replaces all of the parameters in the request instead we should use set method.
And I did not get any of it as I am new to symfony.
What does $request->request->replace() does with the parameter provided to it?
Your $request is an instance of Symfony\Component\HttpFoundation\Request
. Using $request you have access to properties such as request, query, cookies, attributes, files, server, headers. Each of these properties is of type Symfony\Component\HttpFoundation\ParameterBag. Instance of ParameterBag provides access to request parameters using method $request->request->all(). This method will return 'parameters' property of ParameterBag instance.
The $request->request->replace($data) will set 'parameters' property in ParameterBag instance to $data.
Also replace() method does not have any return type that's why when you dumped $request->request->replace($data) you got null as output.
If you want to add some extra parameters to your request then replace() is not the right choice rather you should use set() method in ParameterBag.

What is the best way to create a singleton entity in Symfony 4?

I want to create a settings page, which only has a form in it. If the form is submitted it only updates settings entity but never creates another one. Currently, I achieved this like:
/**
* #param SettingsRepository $settingsRepository
* #return Settings
*/
public function getEntity(SettingsRepository $settingsRepository): Settings
{
$settings = $settingsRepository->find(1);
if($settings == null)
{
$settings = new Settings();
}
return $settings;
}
In SettingsController I call getEntity() method which returns new Settings entity (if the setting were not set yet) or already existing Settings entity (if setting were set at least once).
However my solution is quite ugly and it has hardcoded entity id "1", so I'm looking for a better solution.
Settings controller:
public function index(
Request $request,
SettingsRepository $settingsRepository,
FlashBagInterface $flashBag,
TranslatorInterface $translator,
SettingsService $settingsService
): Response
{
// getEntity() method above
$settings = $settingsService->getEntity($settingsRepository);
$settingsForm = $this->createForm(SettingsType::class, $settings);
$settingsForm->handleRequest($request);
if ($settingsForm->isSubmitted() && $settingsForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($settings);
$em->flush();
return $this->redirectToRoute('app_admin_settings_index');
}
return $this->render(
'admin/settings/index.html.twig',
[
'settings_form' => $settingsForm->createView(),
]
);
}
You could use Doctrine Embeddables here.
Settings, strictly speaking, should not be mapped to entities, since they are not identifiable, nor meant to be. That is, of course, a matter of debate. Really, a Settings object is more of a value object than an entity. Read here for more info.
So, in cases like these better than having a one to one relationship and all that fuzz, you probably will be fine with a simple Value Object called settings, that will be mapped to the database as a Doctrine Embeddable.
You can make this object a singleton by creating instances of it only in factory methods, making the constructor private, preventing cloning and all that. Usually, it is enough only making it immutable, meaning, no behavior can alter it's state. If you need to mutate it, then the method responsible for that should create a new instance of it.
You can have a a method like this Settings::createFromArray() and antoher called Settings::createDefaults() that you will use when you new up an entity: always default config.
Then, the setSettings method on your entity receieves only a settings object as an argument.
If you don't like inmutablity, you can also make setter methods for the Settings object.

Transform GET parameters to clean URL

I use Datatables on fronted to send GET parameters to my Silex application.
Datatables send GET parameters of that type:
champs_societes%5B%5D=naf&zone-geographique=ville&effectif%5B%5D=eff_1a9&effectif%5B%5D=eff_10a19&effectif
%5B%5D=eff_20a49&effectif%5B%5D=eff_plus5000&ca%5B%5D=10k-50k&ca%5B%5D=50k-100k&ca%5B%5D=1kk-2kk&ca%5B
%5D=2kk-5kk&champs_societes%5B%5D=capital_int&fondation%5Bmin%5D=&fondation%5Bmax%5D=&champs_societes
%5B%5D=siren&champs_societes%5B%5D=siret&champs_societes%5B%5D=nature&nature%5B%5D=Etablissement&champs_societes
%5B%5D=formejur&champs_societes%5B%5D=emailg&champs_contacts%5B%5D=emailn&ac_formejur=Artisan-Commer
%C3%A7ant%2CBanque+Populaire%2FLoi+Mars+1917%2CCoop.+%C3%80+Responsabilit%C3%A9+Limit%C3%A9e&ac_naf=0113Z
%2C0121Z%2C0126Z%2C0130Z&ac_departements=14%2C50%2C61%2C68%2C03&ac_villes=77330%2C77680%2C77340&ac_fonction
=Assistant%2CCharg%C3%A9+D'Affaires%2CContr%C3%B4leur+De+Gestion%2CDirecteur+%2F+Responsable
I there a way to genereate a clean URL from this chain ? Ideally by using the Symfony/Silex routing.
Thanks for help
EDIT
I get the GET params above with Request:
$app->post('/ajax/formprocess', function (Request $request) use ($app) {
$df = new Filtres( $request->request->get('dataForm') );
$filtroAdd = $df->getRequest();
I would try with Request class first
Request class from HttpFoundation component (default in Symfony, not sure about Silex as I never used it)
/**
* #param \Symfony\Component\HttpFoundation\Request $request
*/
public function someAction(Request $request)
{
$request->getSchemeAndHttpHost();
$request->getBasePath();
$request->getQueryString(); // this will be the most helpful in your case
// access what you need and build normalized url
}
You should be able to build clean normalized url
Edit, solution for parsing query parameter string to array
$queryParameters = 'query parameters as string to be parsed';
$output = [];
parse_str($queryParameters, $queryParameters);
print_r($queryParameters);
http://php.net/manual/en/function.parse-str.php

In Drupal, how to change the values passed to Pathauto?

I have Pathauto configured to generate an alias based on the title of a node, for a specific content type. The problem is that I want to make small changes in this title before Pathauto uses it to generate the alias.
The first comment in this post suggests the use of hook_token_values, but I couldn't really understand how to use it, even after reading the docs. In my tests, when I implement this hook, the alias generated is always "array", which means I'm missing something.
Any help? Thanks.
It might be that you missed to implement hook_token_list as well. Providing a new token is a two step process:
Implement hook_token_list to declare the tokens you are going to provide. This will just be the name of the tokens, along with a short explanation, and the information to what type of objects the tokens will apply (e.g. node, user, taxonomy, ...)
Implement hook_token_value to actually generate the content of the tokens. This will be called when the tokens are to be replaced with the content they should stand for.
As you just want to provide an alternative version of the title token already provided by the token module, it is probably best to just copy the relevant portions from token_node.inc, stripped down to the relevant cases and adjusted to be used in another module:
/**
* Implementation of hook_token_list().
*/
function yourModule_token_list($type = 'all') {
if ($type == 'node' || $type == 'all') {
$tokens['node']['yourModule-title'] = t('Node title (customized version by yourModule)');
return $tokens;
}
}
This simply says that yourModule provides a token for node objects, named yourModule-title, along with a short description. The main work gets done in the other hook:
/**
* Implementation of hook_token_values().
*/
function yourModule_token_values($type, $object = NULL, $options = array()) {
$values = array();
switch ($type) {
case 'node':
$node = $object;
// TODO: Replace the check_plain() call with your own token value creation logic!
$values['yourModule-title'] = check_plain($node->title);
break;
}
return $values;
}
This will be called whenever the tokens for node objects are needed, with the node in question being passed as the $object parameter (for a user token, the $type would be 'user', and $object would be the user object, and so on for other types). What it does is creating an array of values, keyed by the token name, with the replacement for that token as the value. The original code from token_node.inc just runs the title through check_plain(), so this would be the place to insert your own logic.
In Drupal 7, the token functionality has been moved to core. Tokens are implemented by the hook_tokens and hook_token_info methods. For usage examples, follow the links provided, and look for links to functions that implement hook_tokens and hook_token_info… I found the statistics_tokens and statistics_token_info functions helpful in understanding how this hook works.
It's probably also worth noting that this hook needs to be implemented by a module… my first attempt I dropped my test functions into the theme's template.php, only to have nothing happen at all :-p

Resources