Laravel: How is the local language file loaded? - php

Given the following file structure (the same for en) in Laravel 5:
resources/
de/
list.php
countries.php
mail.php
pages/
pageA.json
pageB.json
I would like to know:
Is __(..) or #lang(..) disturbed from the presence of a folder pages with .json files?
How are the language files loaded?
This is what I managed to find out by myself:
The function __('list.button_send') triggers getFromJson($key, array $replace = [], $locale = null) with $key = 'list.button_send'
This is the main part of the function:
$locale = $locale ?: $this->locale;
// For JSON translations, there is only one file per locale, so we will simply load
// that file and then we will be ready to check the array for the key. These are
// only one level deep so we do not need to do any fancy searching through it.
$this->load('*', '*', $locale);
$line = $this->loaded['*']['*'][$locale][$key] ?? null;
I am already confused by the comment - my translations are in the three different *.php files. Why is Taylor talking about a single JSON file? But if we ignore that and continue, one finds that the load function looks like this:
// The loader is responsible for returning the array of language lines for the
// given namespace, group, and locale. We'll set the lines in this array of
// lines that have already been loaded so that we can easily access them.
$lines = $this->loader->load($locale, $group, $namespace);
$this->loaded[$namespace][$group][$locale] = $lines;
I have no idea why there is a need to call this function with $namespace and $group because it looks to me that they will always be * inside the Translator class. But ignoring that also, I come to the interesting part: What is $lines ? I would guess its an array of the form
$lines = ['list' => ... , 'countries' => ..., 'mail' => ...];
which would answer my questions to:
no they don't interfere
If one access a singe key in a lang, then all language files in that lang (meaning all *.php in that directory) are loaded and stored in the Translator object.
However, I was not able to confirm my guess. In order to do that, I need to find out what $this->loader->load($locale, $group, $namespace); actually does. But $this->loader get assigned by dependency injection in the constructor and I coulnd't find where it is defined.

Your first question Is __(..) or #lang(..) disturbed from the presence of a folder pages with .json files?:
Short answer, no.
$locale = $locale ?: $this->locale;
// For JSON translations, there is only one file per locale, so we will simply load
// that file and then we will be ready to check the array for the key. These are
// only one level deep so we do not need to do any fancy searching through it.
$this->load('*', '*', $locale);
$line = $this->loaded['*']['*'][$locale][$key] ?? null;
In the introduction section of https://laravel.com/docs/8.x/localization#introduction, it states that there are two approaches, one using an array in a php file:
Laravel provides two ways to manage translation strings. First, language > strings may be stored in files within the resources/lang directory. [...]
/resources
/lang
/en
messages.php
/es
messages.php
and another approach using a json file:
Or, translation strings may be defined within JSON files that are placed within the resources/lang directory [...]
/resources
/lang
en.json
es.json
The second approach is convenient, because it does not need keys. However it comes with the downside, that it will contain all language files from all sites in one file.
If you keep digging, you can see from the TranslationServiceProvider that the loader is definied by the FileLoader class. This is the method that loads the json file:
protected function loadJsonPaths($locale)
{
return collect(array_merge($this->jsonPaths, [$this->path]))
->reduce(function ($output, $path) use ($locale) {
if ($this->files->exists($full = "{$path}/{$locale}.json")) {
$decoded = json_decode($this->files->get($full), true);
if (is_null($decoded) || json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException("Translation file [{$full}] contains an invalid JSON structure.");
}
$output = array_merge($output, $decoded);
}
return $output;
}, []);
}
The array $this->jsonPaths is empty per default. So unless you specify the jsonPaths in your app (through the Translator service) your json files inside your pages directory will be ignored.
Your second question How are the language files loaded?
You can load any translatable string through the helper function __($key,..) (you can also provide replacement array and locale and fallback, but we can ignore this for now). This will call app('translator')->get($key,...) The structure of the key is like this:
Namespace::group.key
The "Namespace::" is only relevant for packages https://laravel.com/docs/8.x/packages#translations so within your application, your keys look probably more like this:
group.key
In the first step, it will be checked if the key can be found in current language json (for example resources/lang/en.json) or in default langauge json.
If this cannot be found, then it will be checked if the language file can be found in resources/lang/{lang}/{group}.php. This will be checked for current locale and fallback locale.The group.php will be loaded only once and stored in the FileLoader singlton in memory.
Thus, for the current language and the default language, first the {lang}.json is loaded, if it exists, and then for those languages the resources/lang/{lang}/{group}.php is loaded (if not found in json). Each group will be loaded once per request. If a langauge group is not requested, it will also not be loaded.

Related

multiple language via session, two directories

So I just started web development on my new job, meaning I only have very basic knowledge of php, my html, css and js knowledge is a lot better.
I'm trying to build a basic multiple language site via php sessions but I just can't get it to work the way I want.
I have an index.php file and two more folders called EN and DE in which I have the Englisch.html and the Deutsch.html file. The html files only contain some text and it's my goal to have two buttons (or similar) at the top of my site called EN and DE which switch the session to include the files in said folders and display the text beneeth the buttons and keep the language switch function on every page if I had more then one file per language. By default I want the language to be english so load the Englisch.html first.
I'm guessing I need to create an if else statement on every html file that checks the session so something like: if session EN include Englisch.html elseif session DE include Deutsch.html, and on the index.php I somehow need to set the parameters for the session so something like: startSession EN include Englisch.html startSession DE include Deutsch.html ?
I have no idea how far off I am and any help, espacially actual code examples would be greatly appreciated. I hope this described my problem precisely enough.
Your attempted solution is going to bite you in the long run.
It may seem like an easy solution to switch between different files for different languages, yet assume that your website becomes more dynamic, instead of *.html files you want to deal with *.php files, and then would need to have the same logic inside each of your localized files. It doesn't scale well.
I recommend using a translation libary, there are many available, I had good success with symfony's translation, that you can also include in any php project.
Then translation becomes:
$translatedString = $translator->trans("My content");
And the translation can then be maintained in yaml files and depending on the locale, the right language is chosen and each untranslated string will default to English.
And now, whenever your logic changes it is just at one place where you need to adapt it.
I agree with K0pernikus in that your solution won't scale well. You can write a simple translation library yourself in under an hour and you can then test it to see if it will be robust enough for your needs.
The idea is to have language files without any logic in them. You simply call the file you need. All the logic about which language, file, translation key etc. is contained in your library.
I'll keep it very simple and down to a single file; of course, the actual translations will each need to be stored in a file too.
Directory structure:
/locale/en/messages.php
/locale/fr/messages.php
translator.php (this is the main library file and needs to be included on every page)
Within each messages.php file you need to return an array of translation keys and their respective translation. The translation keys are what you will use in your views. These files will become large with many hundreds or thousands of lines for larger applications. If you intend to keep this home-grown solution you'll need to implement caching. That being said, I have files with hundreds of translations and don't notice any significant performance hit.
<?php
// en
return array(
'applicationName' => 'Matt\'s Marvelous Mysteries',
...
<?php
// fr
return array(
'applicationName' => 'Les merveilleux mystères de Matt',
...
Next, you need a library to read these translations and return the actual translation text to you. This file could simply be a collection of helper functions or a full-blown OOP system.
For simplicity, here is a collection of helper methods that get the job done. It does not implement parameterized substitution and you can add that feature relatively easily by making t() accept a 2nd argument, but that is a whole different topic for another time.
The main method in here is t(). It is very simple and accepts a single translation key. Ex. applicationName or greeting.
Firstly, it tries to determine which language to use. It does this in a sequence of priority: URL, session, browser, fallback.
It first tries to get the language/locale from the URL by looking for
a query string parameter named lang. If you think about it, that makes sense because a user intends to switch their language by clicking a
link that says "English" or "French".
If it doesn't find one in the URL it then moves on to check for one
in the session. Again, if it finds it there it uses it.
If it finds it neither in the URL nor the session, then it checks the
browser/headers from the request.
Finally, if it isn't found in any of those 3 locations then it falls
back to the default language as specified in $defaultLanguage.
Once a language has been found, it puts it into the session so the next request doesn't need to go through all that again. It also loads the appropriate messages.php file based on the discovered language.
Finally, once the language has been found and right file has been loaded into memory it searches for the given $key and returns the appropriate translation. If the $key is not found then it simply returns the given $key which will show up in your views so you know something went horribly wrong and you need to start debugging.
<?php
/**
* Performs the actual translation based on the given key. This is the method that is used
* in the actual views to translate a message.
*
* #param $key
* #return mixed
* #throws Exception
*/
function t($key)
{
$language = getLanguage();
$messages = require "{$_SERVER['DOCUMENT_ROOT']}/locale/{$language}/messages.php";
return (array_key_exists($key, $messages))
? $messages[$key]
: $key;
}
/**
* Returns the language as defined by either the URL, session, or browser setting.
* If a language could not be determined, or is not in a list of supported languages, the default
* language passed in to this method will be returned.
*
* #param string $defaultLanguage
* #return string
*/
function getLanguage($defaultLanguage = 'en')
{
$language = null;
if (isset($_GET['lang'])) {
$language = $_GET['lang'];
} elseif (isset($_SESSION['LANG'])) {
$language = $_SESSION['LANG'];
} else {
$language = getLanguageFromBrowser($defaultLanguage);
}
// If the language given to us is not in our list of supported languages, use the default language.
if (!isset($language) || !in_array($language, getSupportedLanguages())) {
$language = $defaultLanguage;
}
// Store the current language to the session for future use.
$_SESSION['LANG'] = $language;
return $language;
}
/**
* Returns the language that the client's browser is set to use. If we're unable to
* determine a language from the browser this will return the default language passed
* in.
*
* #param string $defaultLanguage
* #return int|string
*/
function getLanguageFromBrowser($defaultLanguage = 'en')
{
$languages = [];
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
// break up string into pieces (languages and q factors)
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
if (count($lang_parse[1])) {
// create a list like "en" => 0.8
$languages = array_combine($lang_parse[1], $lang_parse[4]);
// set default to 1 for any without q factor
foreach ($languages as $lang => $val) {
if ($val === '') $languages[$lang] = 1;
}
// sort list based on value
arsort($languages, SORT_NUMERIC);
}
}
$supportedLanguages = getSupportedLanguages();
foreach ($languages as $locale => $weighting) {
// We're dealing with locale: Ex. en-US
if (preg_match("/[a-z]{2}-[A-Z]{2}/", $locale)) {
$browserLanguage = substr($locale, 0, 2);
} else {
// Probably dealing with a language: Ex. en
$browserLanguage = $locale;
}
if (in_array($browserLanguage, $supportedLanguages)) {
return $browserLanguage;
}
}
return $defaultLanguage;
}
/**
* Returns an array of languages this web application supports.
*
* #return array
*/
function getSupportedLanguages()
{
return [
'en',
'fr'
];
}
To use it, save these methods into a file called translator.php and then include that file in every page you want to use translations.
Sample:
<?php
session_start();
require_once('translator.php');
// Output your language switcheroo-gadget
if (getLanguage() === 'en') {
echo 'French';
} else {
echo 'English';
}
// Your code... blah blah
// Ahh.. Finally, a translation!
echo '<h1>' . t('applicationName') . '</h1>';
Edit
The last thing I will say is that there is a whole world out there of localization, internationalization (often abbreviated as i18n) which you can learn about.
In my example, I simplistically called it language but often people referer to it as locale but that has a different meaning and syntax. For example, there is a difference between en_CA and en_US and en_GB; all of which are English but have regional differences.
echo t('salutation');

Access variables in other files without setting them as globals

I want to have one class that works with configuration settings. Configuration settings are stored inside config/ directory where I want them to be separated to files. When I call my
Config::gi()->getConfig('config_name')
I want the Config class to be able to access the file config/config_name.cfg.php and return the array (from that file) named exactly the same.
This is because I don't want to read all the configuration data if it's not needed. Also, I'm a bit afraid that setting up configuration inside $GLOBALS variable wouldn't be the best solution. I thought about requiring or including those files and then returning their content, but it also seems a bit unprofessional.
What is the best practice to read the configuration like this? Thank you in advance.
For example:
config/routes.cfg.php
$routes => [
'index' => new Route([
// route config here ...
])
];
and to get the routes array I would execute Config::gi()->getConfig('routes'); from helpers/Config.php class.
I'm not sure I would go this route, I would probably load all configs (most likely from a single file) into the class the first time and go from there. Also you can look at parse_ini_file() if you don't want to write out arrays when they change. But for your case, simply:
public function getConfig($name) {
if(file_exists("config/$name.cfg.php")) {
include("config/$name.cfg.php");
return ${$name}; //this translates to $routes in your example
}
return false;
}
Also, the next logical question might be how to save the config when it changes:
public function setConfig($name, $data) {
file_put_contents("config/$name.cfg.php", "$name = " . var_export($data, true) . ";");
}
$routes = array(/* with some stuff in it */);
Config::gi()->setConfig('routes', $routes);

Laravel dynamic config settings

I'm using a package within my project and it stores a setting inside config/packagename
I would like to dynamically change this value inside the config file, this is how the file structure looks currently;
<?php
return [
'view_id' => '118754561',
'cache_lifetime_in_minutes' => 60 * 24,
];
I would like to change it to something like this -
'view_id' => Auth::user()->id,
Can you do this within the config file, or do you have to store some sort of variable to be updated later within a controller. Is there a way to place these variables in an env file and access these new variables from a controller?
This also is a generic solution to dynamically update your .env file (respective the individual key/value pairs)
Change the setting in your config/packagename like so:
return [
'view_id' => env('VIEW_ID', '118754561'),
etc...
]
Add an initial value into .env:
VIEW_ID=118754561
In an appropriate controller (e.g. AuthController), use the code below and call the function like this:
updateDotEnv('VIEW_ID', Auth::User()->id)
protected function updateDotEnv($key, $newValue, $delim='')
{
$path = base_path('.env');
// get old value from current env
$oldValue = env($key);
// was there any change?
if ($oldValue === $newValue) {
return;
}
// rewrite file content with changed data
if (file_exists($path)) {
// replace current value with new value
file_put_contents(
$path, str_replace(
$key.'='.$delim.$oldValue.$delim,
$key.'='.$delim.$newValue.$delim,
file_get_contents($path)
)
);
}
}
(The $delim parameter is needed if you want to make this function more generic in order to work with key=value pairs in .env where the value has to be enclosed in double quotes because they contain spaces).
Admittedly, this might not be a good solution if you have multiple users at the same time using this package in your project. So it depends on what you are using this package for.
NB: You need to make the function public of course if you plan to use it from other classes.
All configuration files of Laravel framework stored in the app/config directory.
so if we need to create custom configuration values it would be better to keep separate our custom configuration in custom file.
so we need to create custom file in app/config directory.
Laravel auto read this file as a config file and will auto manage it
In this topic we are working with custom configuration in laravel and get configuration value in controller or view.
Now i am going to explain how to create a custom config file in Laravel so that we can implement dynamic feature over to this.
create a file in app/config/custom.php which have config keys and value like:-
return array(
'my_val' => 'mysinglelue',
'my_arr_val' => array('1', '2', '3'),
);
Now need to get these config values in view/controller so we will use Config class get() method for this
Syntax:
echo Config::get('filename.arraykey');
where filename is the config file’s name, custom in our case, and key is the array key of the value you’re wanting to access.
In Our case it will be as:
echo Config::get('custom.my_val');
Create run time configuration in laravel :-
Configuration values which are set at run-time are will set for the current request, not be carried over to subsequent requests.
You can pass the dynamic values over here so that you can modify the config file dynamically over here using the isset() functions.
Like how the #Kundan roy as suggested using of the isset() the same condition applies here to. But this one is the alternative method that will work for the dynamic setting of the values in the config.
Config::set('custom.my_val', 'mysinglelue');
Hence by using this method you can create the dynamic config files based on the values that you need.
Since Laravel v5.2 you can dynamically set config values this way:
<?php
config(['app.timezone' => 'America/Chicago']);
$value = config('app.timezone');
echo $value;
// output: 'America/Chicago'
If you want to actually edit the config files (either config/packagename.php or .env) then you may follow matthiku's answer.
However, if I were you, I guess I'd rather want to configure this 3rd party package based on some value defined at runtime, instead of modifying any file (which by the way won't take effect until the next request, when the env values are read again).
So, in my opinion the cleanest way to do this is:
store your desired value in the config data:
config(['packagename.view_id' => Auth::user()->id]);
However, you may notice this probably won't work: the service provider which provides the service you need is often registered before the request is processed, that is, before your config change takes place. So you are still getting the service with old values config.
So, how could you have the service provider be called only when needed and not before (that is, after setting the new config value)? You could make it a deferred provider. Following your example of "spatie laravel-analytics", replace in config/app.php this line:
Spatie\Analytics\AnalyticsServiceProvider::class
with this one:
App\Providers\AnalyticsDeferredServiceProvider::class
and finally create the App\Providers\AnalyticsDeferredServiceProvider class, with:
:
<?php
namespace App\Providers;
use Spatie\Analytics\Analytics;
use Spatie\Analytics\AnalyticsServiceProvider;
class AnalyticsDeferredServiceProvider extends AnalyticsServiceProvider
{
protected $defer = true;
public function provides()
{
return [Analytics::class];
}
}
This way you can have the provider read the config values when you are about to instantiate the service, after you set any runtime config values.
Use this in controller when you need to change.
<?php
use Illuminate\Support\Facades\Config;
//[...]
$user_id = Auth::user()->id;
Config::set('view_id', $user_id );
You can do like this.
In your custom config file. Add the following code You can send your id dynamically from the query string.
'view_id' => isset($_REQUEST['view_id'])?$_REQUEST['view_id']:null,
To get view id
echo config('app.view_id'); // app is config file name
config(['packagename.view_id' => Auth::user()->id]);
Actually if you are that point of code which forces you to make the config values dynamic, then there should be something wrong with your code flow, as the use of config file is just for initializing required values - which should be defined before the app is loaded.
Making config values dynamic is a "BAD PRACTICE" in the world of coding.
So there is the following alternative for your problem.
Define value in .env file (optional)
VIEW_ID=your_view_id_here
Use value inside Controller
$auth_id = auth()->user()->id;
$view_id = env('VIEW_ID', $auth_id);
// If .env file doesn't contains 'VIEW_ID' it will take $auth_user as the view_id
Hope this helps you!
config::set() doesn't works for me in laravel 8. but I got best answer for Create or edit Config file
config(['YOUR-CONFIG.YOUR_KEY' => 'NEW_VALUE']);
$text = '<?php return ' . var_export(config('YOUR-CONFIG'), true) . ';';
file_put_contents(config_path('YOUR-CONFIG.php'), $text);
just try this way this works for me.
original answer you can see here

Get all "pages" in YII?

I'm trying to create my own xml sitemap. Everything is done except for the part that I thought was going to be the easiest. How do you get a list of all the pages on the site? I have a bunch of views in a /site folder and a few others. Is there a way to explicitly request their URLs or perhaps via the controllers?
I do not want to make use of an extension
You can use reflection to iterate through all methods of all your controllers:
Yii::import('application.controllers.*');
$urls = array();
$directory = Yii::getPathOfAlias('application.controllers');
$iterator = new DirectoryIterator($directory);
foreach ($iterator as $fileinfo)
{
if ($fileinfo->isFile() and $fileinfo->getExtension() == 'php')
{
$className = substr($fileinfo->getFilename(), 0, -4); //strip extension
$class = new ReflectionClass($className);
foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method)
{
$methodName = $method->getName();
//only take methods that begin with 'action', but skip actions() method
if (strpos($methodName, 'action') === 0 and $methodName != 'actions')
{
$controller = lcfirst(substr($className, 0, strrpos($className, 'Controller')));
$action = lcfirst(substr($methodName, 6));
$urls[] = Yii::app()->createAbsoluteUrl("$controller/$action");
}
}
}
}
You need to know what content you want to include in your sitemap.xml, I don't really think you want to include ALL pages in your sitemap.xml, or do you really want to include something like site.com/article/edit/1 ?
That said, you may only want the result from the view action in your controllers. truth is, you need to know what you want to indexed.
Do not think in terms of controllers/actions/views, but rather think of the resources in your system that you want indexed, be them articles, or pages, they are all in your database or stored somehow, so you can list them, and they have a URI that identifies them, getting the URI is a matter of invoking a couple functions.
There are two possiblities -
Case 1:
You are running a static website then you can find all your HTML inside 1 folder - protected/views/site/pages
http://www.yiiframework.com/wiki/22/how-to-display-static-pages-in-yii/
Case 2:
Website is dynamic. Tasks such as generating and regenerating Sitemaps can be classified into background tasks.
Running background taks can be achieved by emulating the browser which is possible in linux using - WGET, GET or lynx commands
Or, You can create a CronController as a CConsoleCommand. How to use Commands in YII is shown in link below -
http://tariffstreet.com/yii/2012/04/implementing-cron-jobs-with-yii-and-cconsolecommand/
Sitemap is an XML which lists your site's URL. But it does more than that.
It helps you visualize the structure of a website , you may have
category
subcategories.
While making a useful extension, above points can be kept into consideration before design.
Frameworks like Wordpress provide way to generate categorical sitemap.
So the metadata for each page is stored from before and using that metadata it discovers and group pages.
Solution by Reflection suggested by #Pavle is good and should be the way to go.
Consider there may be partial views and you may or may not want to list them as separate links.
So how much effort you want to put into creating the extension is subject to some of these as well.
You may either ask user to list down all variables in config fie and go from there which is not bad or you have to group pages and list using some techniques like reflection and parsing pages and looking for regex.
For ex - Based on module names you can group them first and controllers inside a module can form sub-group.
One first approach could be to iterate over the view files, but then you have to take into account that in some cases, views are not page destinations, but page sections included in another pages by using CController::renderPartial() method. By exploring CController's Class Reference I came upon the CController::actions() method.
So, I have not found any Yii way to iterate over all the actions of a CController, but I used php to iterate over all the methods of a SiteController in one of my projects and filter them to these with the prefix 'action', which is my action prefix, here's the sample
class SiteController extends Controller{
public function actionTest(){
echo '<h1>Test Page!</h1></p>';
$methods = get_class_methods(get_class($this));
// The action prefix is strlen('action') = 6
$actionPrefix = 'action';
$reversedActionPrefix = strrev($actionPrefix);
$actionPrefixLength = strlen($actionPrefix);
foreach ($methods as $index=>$methodName){
//Always unset actions(), since it is not a controller action itself and it has the prefix 'action'
if ($methodName==='actions') {
unset($methods[$index]);
continue;
}
$reversedMethod = strrev($methodName);
/* if the last 6 characters of the reversed substring === 'noitca',
* it means that $method Name corresponds to a Controller Action,
* otherwise it is an inherited method and must be unset.
*/
if (substr($reversedMethod, -$actionPrefixLength)!==$reversedActionPrefix){
unset($methods[$index]);
} else $methods[$index] = strrev(str_replace($reversedActionPrefix, '', $reversedMethod,$replace=1));
}
echo 'Actions '.CHtml::listBox('methods', NULL, $methods);
}
...
}
And the output I got was..
I'm sure it can be furtherly refined, but this method should work for any of the controllers you have...
So what you have to do is:
For each Controller: Filter out all the not-action methods of the class, using the above method. You can build an associative array like
array(
'controllerName1'=>array(
'action1_1',
'action1_2'),
'controllerName2'=>array(
'action2_1',
'action2_2'),
);
I would add a static method getAllActions() in my SiteController for this.
get_class_methods, get_class, strrev and strlen are all PHP functions.
Based on your question:
1. How do you get a list of all the pages on the site?
Based on Yii's way of module/controller/action/action_params and your need to construct a sitemap for SEO.
It will be difficult to parse automatically to get all the urls as your action params varies indefinitely. Though you could simply get controller/action easily as constructed by
Pavle Predic. The complexity comes along when you have customized (SERF) URL rules meant for SEO.
The next best solution is to have a database of contents and you know how to get each content via url rules, then a cron console job to create all the urls to be saved as sitemap.xml.
Hope this helps!

Symfony 2: How to get route defaults by route name?

Is it possible to retrieve info about a certain route by its name, or get a list of all routes?
I need to be able to fetch the _controller value in defaults for any route, not only the current.
Is this possible and how?
P.S.: I found I could get the path to the routes YAML being used, but reparsing it seems unnecessary and heavy.
I am really good at answering my own questions..
To get routes use getRouteCollection() on the router ($this -> get('router') -> getRouteCollection() inside a controller), then you get RouteCollection instance on which you can all() or get($name).
As described in my comment above Router::getRouteCollection is really slow and not intended for use in production code.
So if you really need it fast, you have to hack your way through it. Be warned, this is going to be hackish:
Directly accessing the dumped route data
To speed up route matching, Symfony compiles all static routes into one big PHP class file. This file is generated by Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper and declares a Symfony\Component\Routing\Generator\UrlGenerator that stores all route definitions in a private static called $declaredRoutes.
$declaredRoutes is an array of compiled route fields indexed by the route's name. Among others (see below) these fields also contain the route defaults.
In order to access $declaredRoutes we have to use a \ReflectionProperty.
So the actual code is:
// If you don't use a custom Router (e.g., a chained router) you normally
// get the Symfony router from the container using:
// $symfonyRouter = $container->get('router');
// After that, you need to get the UrlGenerator from it.
$generator = $symfonyRouter->getGenerator();
// Now read the dumped routes.
$reflectionProperty = new \ReflectionProperty($generator, 'declaredRoutes');
$reflectionProperty->setAccessible(true);
$dumpedRoutes = $reflectionProperty->getValue($generator);
// The defaults are at index #1 of the route array (see below).
$routeDefaults = $dumpedRoutes['my_route'][1];
Fields of the route array
The fields of each route are filled by the above mentioned Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper like this:
// [...]
$compiledRoute = $route->compile();
$properties = array();
$properties[] = $compiledRoute->getVariables();
$properties[] = $route->getDefaults();
$properties[] = $route->getRequirements();
$properties[] = $compiledRoute->getTokens();
$properties[] = $compiledRoute->getHostTokens();
$properties[] = $route->getSchemes();
// [...]
So to access its requirements you would use:
$routeRequirements = $dumpedRoutes['my_route'][2];
Bottom line
I've looked through the Symfony manual, the source code, forums, stackoverflow etc. but still wasn't able to find a better way of doing this.
It's brutal, ignores the API, and might break in future updates (although it hasn't changed in the most recent Symfony 4.1: PhpGeneratorDumper on GitHub).
But it's rather short and fast enough to be used in production.

Categories