I'd like to have multilingual site done with Symfony. So I need to add something like:
# app/config/routing.yml
contact:
path: /{_locale}
defaults: { _locale: "pl" }
requirements:
_locale: pl|en
However I don't want to repeat this on every route I define, so I came up with solution:
# app/config/config.yml
parameters:
locale.available: pl|en
locale.default: pl
<...>
router:
resource: "%kernel.root_dir%/config/routing.php" # note '.php'
<!-- language: lang-php -->
# app/config/routing.php
<?php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use Symfony\Component\Yaml\Parser;
$configFile = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'routing.yml';
$yaml = new Parser();
try {
$routes = $yaml->parse(file_get_contents($configFile));
} catch (ParseException $e) {
printf("Unable to parse the YAML string: %s", $e->getMessage());
}
$collection = new RouteCollection();
foreach ($routes as $name => $def) {
$route = new Route($def['path']);
foreach (['defaults', 'requirements', 'options', 'host', 'schemes', 'methods', 'condition'] as $opt) {
$mtd = 'add'. ucfirst($opt);
if(isset($def[$opt])) $route->$mtd($def[$opt]);
}
$collection->add($name, $route);
}
$collection->addRequirements(['_locale' => "%locale.available%"]);
$collection->addDefaults(['_locale'=>"%locale.default%"]);
return $collection;
?>
# app/config/routing_dev.yml
<...>
_main:
resource: routing.php
Still, I'm rather new to Symfony2 (now 3), so I wonder... Is there a better way to accomplish such appending route config to all the routes? Perhaps there are more flexible or more "right" ways of doing such things in Symfony? Or should I hook into some existing mechanism?
You could use a custom route loader that extends the default one. See here for more info:
http://symfony.com/doc/current/cookbook/routing/custom_route_loader.html
For example if you wanna support yml, xml and annotations you'd need to extend the Symfony\Component\Routing\Loader\YamlFileLoader, Symfony\Component\Routing\Loader\XmlFileLoader and Sensio\Bundle\FrameworkExtraBundle\Routing\AnnotatedRouteControllerLoader incorporate the logic you have to the load method and you'd need to change the
sensio_framework_extra.routing.loader.annot_class.class,routing.loader.yml.class and routing.loader.xml.class
parameters to point to your new classes.
Defining your own route_loader seems like the less hacky solution
Related
Following the Dynamic Router doc, I can:
Create a route linked to my content (Page entity)
Persist it with ORM
Load my controller matching the route
But as my controller should expect the $contentDocument parameter, I have nothing.
Here's how I create my route and my entity:
$page = new Page();
$page->setTitle('My Content')
->setBackground(1)
->setDescription('My description')
->setContent('<h1>Hello</h1>');
$manager->persist($page);
$manager->flush(); // flush to be able to use the generated id
$contentRepository = $this->container->get('cmf_routing.content_repository');
$route = new Route();
$route->setName('my-content');
$route->setStaticPrefix('/my-content');
$route->setDefault(RouteObjectInterface::CONTENT_ID, $contentRepository->getContentId($page));
$route->setContent($page);
$page->addRoute($route); // Create the backlink from content to route
$manager->persist($page);
$manager->flush();
Here's my config section:
cmf_routing:
chain:
routers_by_id:
router.default: 200
cmf_routing.dynamic_router: 100
dynamic:
enabled: true
persistence:
orm:
enabled: true
generic_controller: AppBundle:Default:showPage
templates_by_class:
AppBundle\Entity\Page: AppBundle:Default:index.html.twig
My controller:
public function showPageAction($page)
{
return $this->render('default/index.html.twig');
}
And the error:
Controller "AppBundle\Controller\DefaultController::showPageAction()" requires that you provide a value for the "$page" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.
Dynamic routing puts the content document into the request with the name "contentDocument". You need to explicitly use that name:
public function showPageAction($contentDocument)
{
return $this->render('default/index.html.twig');
}
if you do not need to do anything in your controller, you don't need to specify the generic_controller. template_by_class will make the bundle provided controller render that template with the page instance found in $contentDocument.
NelmioApiDocBundle is allowing only single configuration file as .yml as.
nelmio_api_doc:
routes:
path_patterns: # an array of regexps
- ^/api
documentation:
paths:
/api/login_check:
...
/api/refresh_token:
...
But I have more then 200 URL to use and all for different Bundles.
it would working properly but hard to handle all in same file.
So if anyone has solution to divide "paths" as different separate files.
I can't see the config options "routes" and "documentation" in the configuration reference of NelmioApiDocBundle
You should instead use Annotations, as described in the docs.
Problem solved! :-)
One of Mine friend has a great idea to resolve this problem. actually, this is not the proper solution, but I this is the only way to solve this problem.
We create "api_index.yaml"
export_path: '/config/packages/api_doc.yaml'
import_paths:
- "#DomCoreBundle/Resources/config/api_doc/api_base_doc.yaml"
- "#DomCmsBundle/Resources/config/api_doc/static_page_path_doc.yaml"
- "#DomEntityBundle/Resources/config/api_doc/category_path_doc.yaml"
- "#DomCmsBundle/Resources/config/api_doc/carousel_path_doc.yaml"
- "#DomQuickLinkBundle/Resources/config/api_doc/quick_link_path_doc.yaml"
- "#DomUserBundle/Resources/config/api_doc/user_path_doc.yaml"
- "#DomUserBundle/Resources/config/api_doc/dealer_path_doc.yaml"
...
Then we create a Symfony command(script) which read each "import_paths" file and append content in "export_path" file.
$this->io = new SymfonyStyle($input, $output);
$path = $this->kernel->locateResource('#FaCoreBundle/Resources/config/api_index.yaml');
$paths = Yaml::parse(file_get_contents($path));
if (array_key_exists('import_paths', $paths)) {
$contentLength = $this->loadFilesByPath($input, $output, $paths);
if ($input->getOption('watch')) {
$contentLengthNew = [];
while (true) {
foreach ($paths['import_paths'] as $path) {
$ymlPath = $this->kernel->locateResource($path);
$contentLengthNew[$ymlPath] = md5((file_get_contents($ymlPath)));
}
if (!empty(array_diff($contentLength, $contentLengthNew)) || count($contentLength) != count($contentLengthNew)) {
$diff = array_diff($contentLengthNew, $contentLength);
if (!empty($diff)) {
$this->io->writeln(sprintf('<comment>%s</comment> <info>[file+]</info> %s', date('H:i:s'), current(array_keys($diff))));
}
$contentLength = $this->loadFilesByPath($input, $output, $paths);
}
sleep($input->getOption('period'));
}
}
}
A simply way is to read yaml files from a not autoload area, merge the content and write a single file that will be loaded.
SF 4.x
/config
/NELMIOApiDocDefinitions // out of autowire
/one.yaml
/two.yaml
/packages
/_NELMIOApiDocDefinitions.yaml // file_put_content()
one.yaml
nelmio_api_doc:
areas:
path_patterns:
- /one
documentation:
tags:
- { name: One, description: '...' }
paths:
/onepath/:
...
Kernel.php
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
$this->mergeNELMIOApiDocYamlDefinitions();
...
}
private function mergeNELMIOApiDocYamlDefinitions()
{
// dev, prod, ...
$kernelEnvironnement = $this->getEnvironment();
// No need to rebuild definitions in production
if ($kernelEnvironnement == 'prod')
return;
// Path to the configuration directory
$confDir = $this->getProjectDir() . '/config';
// Path to the documentations files to merge
$yamlDefinitionsDirectory = $confDir . '/NELMIOApiDocDefinitions';
// Get the files to merge (without dots directories)
$yamlDefinitionsFiles = array_diff(scandir($yamlDefinitionsDirectory), ['.', '..']);
// Read definitions files and merge key with theirs values
$mergedDefinitions = [];
foreach ($yamlDefinitionsFiles as $yamlDefinitionFile) {
$yamlFileContent = Yaml::parseFile($yamlDefinitionsDirectory . '/' . $yamlDefinitionFile);
$mergedDefinitions = array_merge_recursive($mergedDefinitions, $yamlFileContent);
}
// Build the YAML
$yaml = Yaml::dump($mergedDefinitions);
// Write the YAML into a single file to be loaded by Symfony
file_put_contents($confDir . '/packages/_NELMIOApiDocDefinitions.yaml', $yaml);
}
Problem solved with
config/packages/nelmio.yaml
imports:
- {resource: '../nelmio/default.yaml'}
- {resource: '../nelmio/v1.yaml'}
config/nelmio/default.yaml
nelmio_api_doc:
areas:
default:
path_patterns: [ ^/default/ ]
documentation:
info:
title: Default
config/nelmio/v1.yaml
nelmio_api_doc:
areas:
default:
path_patterns: [ ^/v1/ ]
documentation:
info:
title: V1
im trying to make a simple custom repository in order to understand how elastic search repository works. the documentation is pretty straight forward but i still dont understand how it works, im getting this error ´The service definition "fos_elastica.manager" does not exist.´. so far i think my problem is in the controller since i dont understand how to intialize them, also i would like to know if im in the right way in my configuration of the custom repository and the simple query i made.
im getting this error with this configuration whenever i try to make a search,
The service definition "fos_elastica.manager" does not exist.
this is my configuration so far:
//app/config.yml
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
sava:
client: default
types:
blog:
mappings:
id:
type: integer
body : ~
title : ~
tags: ~
persistence:
identifier: id
driver: orm
model: sava\BlogBundle\Entity\TblPost
finder: ~
provider: ~
listener: ~
repository: sava\BlogBundle\SearchRepository\TblPostRepository
this is my controller action:
namespace sava\BlogBundle\Controller;
//custom querys
use FOS\ElasticaBundle\Manager\RepositoryManager;
use FOS\ElasticaBundle\Repository;
//
use Symfony\Component\DependencyInjection\ContainerBuilder;
class TblPostController extends Controller
{
public function getPostAction(Request $request)
{
$container = new ContainerBuilder();
$repositoryManager = $container->get('fos_elastica.manager');
$repository = $repositoryManager->getRepository('BlogBundle:TblPost');
$items2 = $repository->matchExact($categoria,$searchQuery );
return $this->render('savaBlogBundle:TblPost:index.html.twig', array(
'results' => $items2, 'entities' => $items2
));
}
this is my post repository:
<?php
namespace sava\BlogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\ElasticaBundle\Repository;
class TblPostRepository extends FOS\ElasticaBundle\Repository
{
public function matchExact($campo, $searchQuery) {
//$finder = $this->get('fos_elastica.finder.sava.blog');
$query = new Query();
if($searchQuery=='')
{
$innerQuery = new Query\MatchAll();
}
else{
$innerQuery = new Query\Match();
$innerQuery->setField( $campo , array('query' => $searchQuery));
}
$query->setQuery($innerQuery);
$query->setSize(1000000);
$query->setExplain(true);
return $this->find($query);
}
}
and since im using yml this is my tblpost.orm, i did generate my entities.
whenever i do the get postaction it throws me that it cant find the container, and i dont see an example in how to properly intiaze it, also is this is how you make a custom query?
EDIT 1:
so i changed this:
$container = new ContainerBuilder();
$repositoryManager = $container->get('fos_elastica.manager');
to this:
$elastica = $this->container->get('fos_elastica.manager');// single entry point, no fancy services
$SearchRepository = $elastica->getRepository('savaBlogBundle:TblPostRepository');// single type
and im getting this error:
No search finder configured for sava\BlogBundle\Entity\TblPostRepository
I've just had the same issue, the soultion is that instead of savaBlogBundle:TblPostRepository you should use your entity, for example:
$SearchRepository = $elastica->getRepository('savaBlogBundle:TblPost`)
According your fatal error in title of the issue enter link description here
The main problem why did you get that mistake is that, you assigned the same TblPostRepository in (doctrine config for entity) and in fos_elastica.
I know that the basis of Silex approach in which all the application logic in a single file. But my application will be possible to have more than twenty controllers. So I want to have a handy map to manage the router.
My question is to search for solutions in which I would be able to make a router to a separate file. In the best case, the file must be of YAML type:
# config/routing.yml
_home:
pattern: /
defaults: { _controller: MyProject\Controller\MyController::index }
But the native is also a good case (for me):
$routes = new RouteCollection();
$routes->add(
'home',
new Route('/', array('controller' => 'MyProject\Controller\MyController::index')
));
return $routes;
Problem of the second case is that I have to use the match() function for each rule of routing. It is not at all clear.
What are the ways to solve this issue? The condition is that I want to use the existing API Silex or components of Symfony2.
Small note:
I don't use a ControllerProviderInterface for my Controller classes. This is an independent classes.
First of all, the basis of Silex is not that you put everything in one file. The basis of Silex is that you create your own 'framework', your own way of organizing applications.
"Use silex if you are comfortable with making all of your own architecture decisions and full stack Symfony2 if not."
-- Dustin Whittle
Read more about this in this blogpost, created by the creator of Silex.
How to solve your problem
What you basically want is to parse a Yaml file and get the pattern and defaults._controller settings from each route that is parsed.
To parse a Yaml file, you can use the Yaml Component of Symfony2. You get an array back which you can use to add the route to Silex:
// parse the yaml file
$routes = ...;
$app = new Silex\Application();
foreach ($routes as $route) {
$app->match($route['pattern'], $route['defaults']['_controller']);
}
// ...
$app->run();
I thought I'd add my method here as, although others may work, there isn't really a simple solution. Adding FileLocator / YamlFileLoader adds a load of bulk that I don't want in my application just to read / parse a yaml file.
Composer
First, you're going to need to include the relevant files. The symfony YAML component, and a really simple and useful config service provider by someone who actively works on Silex.
"require": {
"symfony/yaml": "~2.3",
"igorw/config-service-provider": "1.2.*"
}
File
Let's say that your routes file looks like this (routes.yml):
config.routes:
dashboard:
pattern: /
defaults: { _controller: 'IndexController::indexAction' }
method: GET
Registration
Individually register each yaml file. The first key in the file is the name it will be available under your $app variable (handled by the pimple service locator).
$this->register(new ConfigServiceProvider(__DIR__."/../config/services.yml"));
$this->register(new ConfigServiceProvider(__DIR__."/../config/routes.yml"));
// any more yaml files you like
Routes
You can get these routes using the following:
$routes = $app['config.routes']; // See the first key in the yaml file for this name
foreach ($routes as $name => $route)
{
$app->match($route['pattern'], $route['defaults']['_controller'])->bind($name)->method(isset($route['method'])?$route['method']:'GET');
}
->bind() allows you to 'name' your urls to be used within twig, for example.
->method() allows you to specify POST | GET. You'll note that I defaulted it to 'GET' with a ternary there if the route doesn't specify a method.
Ok, that's how I solved it.
This method is part of my application and called before run():
# /src/Application.php
...
protected function _initRoutes()
{
$locator = new FileLocator(__DIR__.'/config');
$loader = new YamlFileLoader($locator);
$this['routes'] = $loader->load('routes.yml');
}
Application class is my own and it extends Silex\Application.
Configuration file:
# /src/config/routes.yml
home:
pattern: /
defaults: { _controller: '\MyDemoSite\Controllers\DefaultController::indexAction' }
It works fine for me!
UPD:
I think this is the right option to add collections:
$this['routes']->addCollection($loader->load('routes.yml'));
More flexible.
You could extend the routes service (which is a RouteCollection), and load a YAML file with FileLocator and YamlFileLoader:
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\Loader\YamlFileLoader;
$app->extend('routes', function($routeCollection) {
$locator = new FileLocator([__DIR__ . '/../config']);
$loader = new YamlFileLoader($locator);
$collection = $loader->load('routes.yml');
$routeCollection->addCollection($collection);
return $routeCollection;
});
You will need symfony/config and symfony/yaml dependencies though.
I have added a setting to my config.yml file as such:
app.config:
contact_email: somebody#gmail.com
...
For the life of me, I can't figure out how to read it into a variable. I tried something like this in one of my controllers:
$recipient =
$this->container->getParameter('contact_email');
But I get an error saying:
The parameter "contact_email" must be
defined.
I've cleared my cache, I also looked everywhere on the Symfony2 reloaded site documentation, but I can't find out how to do this.
Probably just too tired to figure this out now. Can anyone help with this?
Rather than defining contact_email within app.config, define it in a parameters entry:
parameters:
contact_email: somebody#gmail.com
You should find the call you are making within your controller now works.
While the solution of moving the contact_email to parameters.yml is easy, as proposed in other answers, that can easily clutter your parameters file if you deal with many bundles or if you deal with nested blocks of configuration.
First, I'll answer strictly the question.
Later, I'll give an approach for getting those configs from services without ever passing via a common space as parameters.
FIRST APPROACH: Separated config block, getting it as a parameter
With an extension (more on extensions here) you can keep this easily "separated" into different blocks in the config.yml and then inject that as a parameter gettable from the controller.
Inside your Extension class inside the DependencyInjection directory write this:
class MyNiceProjectExtension extends Extension
{
public function load( array $configs, ContainerBuilder $container )
{
// The next 2 lines are pretty common to all Extension templates.
$configuration = new Configuration();
$processedConfig = $this->processConfiguration( $configuration, $configs );
// This is the KEY TO YOUR ANSWER
$container->setParameter( 'my_nice_project.contact_email', $processedConfig[ 'contact_email' ] );
// Other stuff like loading services.yml
}
Then in your config.yml, config_dev.yml and so you can set
my_nice_project:
contact_email: someone#example.com
To be able to process that config.yml inside your MyNiceBundleExtension you'll also need a Configuration class in the same namespace:
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root( 'my_nice_project' );
$rootNode->children()->scalarNode( 'contact_email' )->end();
return $treeBuilder;
}
}
Then you can get the config from your controller, as you desired in your original question, but keeping the parameters.yml clean, and setting it in the config.yml in separated sections:
$recipient = $this->container->getParameter( 'my_nice_project.contact_email' );
SECOND APPROACH: Separated config block, injecting the config into a service
For readers looking for something similar but for getting the config from a service, there is even a nicer way that never clutters the "paramaters" common space and does even not need the container to be passed to the service (passing the whole container is practice to avoid).
This trick above still "injects" into the parameters space your config.
Nevertheless, after loading your definition of the service, you could add a method-call like for example setConfig() that injects that block only to the service.
For example, in the Extension class:
class MyNiceProjectExtension extends Extension
{
public function load( array $configs, ContainerBuilder $container )
{
$configuration = new Configuration();
$processedConfig = $this->processConfiguration( $configuration, $configs );
// Do not add a paramater now, just continue reading the services.
$loader = new YamlFileLoader( $container, new FileLocator( __DIR__ . '/../Resources/config' ) );
$loader->load( 'services.yml' );
// Once the services definition are read, get your service and add a method call to setConfig()
$sillyServiceDefintion = $container->getDefinition( 'my.niceproject.sillymanager' );
$sillyServiceDefintion->addMethodCall( 'setConfig', array( $processedConfig[ 'contact_email' ] ) );
}
}
Then in your services.yml you define your service as usual, without any absolute change:
services:
my.niceproject.sillymanager:
class: My\NiceProjectBundle\Model\SillyManager
arguments: []
And then in your SillyManager class, just add the method:
class SillyManager
{
private $contact_email;
public function setConfig( $newConfigContactEmail )
{
$this->contact_email = $newConfigContactEmail;
}
}
Note that this also works for arrays instead of scalar values! Imagine that you configure a rabbit queue and need host, user and password:
my_nice_project:
amqp:
host: 192.168.33.55
user: guest
password: guest
Of course you need to change your Tree, but then you can do:
$sillyServiceDefintion->addMethodCall( 'setConfig', array( $processedConfig[ 'amqp' ] ) );
and then in the service do:
class SillyManager
{
private $host;
private $user;
private $password;
public function setConfig( $config )
{
$this->host = $config[ 'host' ];
$this->user = $config[ 'user' ];
$this->password = $config[ 'password' ];
}
}
I have to add to the answer of douglas, you can access the global config, but symfony translates some parameters, for example:
# config.yml
...
framework:
session:
domain: 'localhost'
...
are
$this->container->parameters['session.storage.options']['domain'];
You can use var_dump to search an specified key or value.
In order to be able to expose some configuration parameters for your bundle you should consult the documentation for doing so. It's fairly easy to do :)
Here's the link: How to expose a Semantic Configuration for a Bundle
Like it was saying previously - you can access any parameters by using injection container and use its parameter property.
"Symfony - Working with Container Service Definitions" is a good article about it.
I learnt a easy way from code example of http://tutorial.symblog.co.uk/
1) notice the ZendeskBlueFormBundle and file location
# myproject/app/config/config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: #ZendeskBlueFormBundle/Resources/config/config.yml }
framework:
2) notice Zendesk_BlueForm.emails.contact_email and file location
# myproject/src/Zendesk/BlueFormBundle/Resources/config/config.yml
parameters:
# Zendesk contact email address
Zendesk_BlueForm.emails.contact_email: dunnleaddress#gmail.com
3) notice how i get it in $client and file location of controller
# myproject/src/Zendesk/BlueFormBundle/Controller/PageController.php
public function blueFormAction($name, $arg1, $arg2, $arg3, Request $request)
{
$client = new ZendeskAPI($this->container->getParameter("Zendesk_BlueForm.emails.contact_email"));
...
}
Inside a controller:
$this->container->getParameter('configname')
to get the config from config/config.yaml:
parameters:
configname: configvalue