When running a Symfony app in the dev environment, the web debug toolbar allows me to see how many queries that Doctrine generated. Is there a similar profiler option for Console commands?
As described in the docs, the profiler only collects information for all requests. I believe there is no collector for the console commands. One of the ways to get more insight into the queries executed by Doctrine is to check your log files. For example, you can do something like this on Unix based systems:
tail -f app/logs/dev.log | grep doctrine
Also see: http://symfony.com/doc/current/book/internals.html#profiler
Yeah, --verbose is useful as #manolo mentioned. You can control what gets output in -v -vv -vvv from the monolog handler config
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
console:
type: console
bubble: false
verbosity_levels:
VERBOSITY_VERBOSE: INFO
VERBOSITY_VERY_VERBOSE: DEBUG
channels: ["!doctrine"]
console_very_verbose:
type: console
bubble: false
verbosity_levels:
VERBOSITY_VERBOSE: NOTICE
VERBOSITY_VERY_VERBOSE: NOTICE
VERBOSITY_DEBUG: DEBUG
channels: ["doctrine"]
Notice how you can even disable a channel -v or --verbose will only output non doctrine logs at the specified verbose levels.
First of all, symfony profiler depends on Request. Thats why its cant be used in console commands out of the box and, probably, it will not be fixed. Related symfony issue
But you still can access default DBAL profiling logger. It should be instance of Doctrine\DBAL\Logging\DebugStack
It have public queries property, which hold all executed queries, parameters, execution time etc.
Whenever you will need to debug actual queries - you can do in such a way
/** #var $em Registry */
$em = $this->getContainer()->get('doctrine')->getManager();
$profiler = $this->getContainer()->get('doctrine.dbal.logger.profiling.default');
$shop = $em->getRepository(Shop::class)->find(7);
$sku = $em->getRepository(Sku::class)->find(45);
// clear profiles data, we want to profile Criteria below
$profiler->queries = [];
$shopWares = $shop->getShopWarePagesForSku($sku);
$output->writeln(var_export($profiler->queries));
It would generate output like
array (
3 =>
array (
'sql' => 'SELECT ...... FROM ShopWarePage t0 WHERE (t0.sku_id = ? AND t0.sku_id = ?)',
'params' =>
array (
0 => 45,
1 => 7,
),
'types' =>
array (
0 => 'integer',
1 => 'integer',
),
'executionMS' => 0.00075292587280273438,
),
)
It's possible to run a command from the controller or other services. So all command information will be in the profiler.
There is an example from symfony docs
// src/Controller/DebugTwigController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
class DebugTwigController extends AbstractController
{
public function debugTwig(KernelInterface $kernel): Response
{
$application = new Application($kernel);
$application->setAutoExit(false);
$input = new ArrayInput([
'command' => 'debug:twig',
// (optional) define the value of command arguments
'fooArgument' => 'barValue',
// (optional) pass options to the command
'--bar' => 'fooValue',
]);
// You can use NullOutput() if you don't need the output
$output = new BufferedOutput();
$application->run($input, $output);
// return the output, don't use if you used NullOutput()
$content = $output->fetch();
// return new Response(""), if you used NullOutput()
return new Response($content);
}
}
Related
I'm a newbie in Symfony.
I trying to change Monolog output formatter by console argument 'format=json'.
In short, I want to run any console command with the way:
app/console my_command --format=json # xml / txt / my own
...and get output of LoggerInterface in requested format.
For example, I set the default formatter in configuration:
monolog:
handlers:
console:
type: console
channels: [!event, !doctrine]
formatter: json_formatter
services:
json_formatter:
class: Monolog\Formatter\JsonFormatter
When I create the some MyEventListener::onConsoleCommand (as described here), I cannot change the parameters bag because it is already compiled: "Impossible to call set() on a frozen ParameterBag."
Up2: My services config in this case looks like this:
services:
kernel.listener.command_dispatch:
class: My\Listener\MyEventListener
autowire: true
tags:
- { name: kernel.event_listener, event: console.command }
With another way, I can register console option inside initial file:
# app/console
$loader = require __DIR__.'/autoload.php';
# ...
$application->getDefinition()->addOption(
new InputOption(
'formatter',
'f',
InputOption::VALUE_OPTIONAL,
'The logs output formatter',
'json_formatter'
)
);
But I can't find a way to change parameters bag in the Container. Because $application->getKernel()->getContainer() is still empty.
So, how to change Symfony2 parameters from console input?
Alternatively, maybe I can just use some environments parameters? But how I can get an environment variable in YAML configuration?
Thank you.
UP3:
I have achieved the goal with environments variables like this:
SYMFONY__LOG__FORMATTER=json_formatter app/console my_command
monolog:
handlers:
console:
type: console
#...
formatter: '%log.formatter%'
The only point to modify command arguments for every registered command of your application is handling CommandEvents::COMMAND that is triggered before any command has been executed. So you can modify its arguments and read them as described here. Also, at this point you have your container compiled and it is not possible to modify service's definitions at this point. But you can get any service.
So i think you can end up with following handler:
class LogFormatterEventListener
{
private $container;
private $consoleHandler;
public function __construct(ContainerInterface $container, HandlerInterface $consoleHandler)
{
$this->container = $container;
$this->consoleHandler = $consoleHandler;
}
public function onConsoleCommand(ConsoleCommandEvent $event)
{
$inputDefinition = $event->getCommand()->getApplication()->getDefinition();
$inputDefinition->addOption(
new InputOption('logformat', null, InputOption::VALUE_OPTIONAL, 'Format of your logs', null)
);
// merge the application's input definition
$event->getCommand()->mergeApplicationDefinition();
$input = new ArgvInput();
// we use the input definition of the command
$input->bind($event->getCommand()->getDefinition());
$formatter = $input->getOption('logformat');
if ($formatter /** && $this->container->has($formatter) **/) {
$this->consoleHandler->setFormatter(
$this->container->get($formatter);
);
}
}
}
Here is alternative solution (compatible with common):
Configuration:
monolog:
handlers:
console:
type: console
channels: [!event, !doctrine]
formatter: "%log.formatter%"
services:
json_formatter:
class: Monolog\Formatter\JsonFormatter
Command execution:
# colored plain text
app/console my_command
# json
SYMFONY__LOG__FORMATTER=json_formatter app/console my_command
am creating own framework based on Sf2 commponents and i try to create router service.
I i need that service for generateUrl() method
protected function generateUrl($route, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
{
return $this->get('router')->generate($route, $parameters, $referenceType);
}
I try this
$container = new ContainerBuilder();
$container->setDefinition('router_loader', new Definition('Symfony\Component\Config\Loader\LoaderInterface'));
$container->setDefinition('router', new Definition('Symfony\Component\Routing\Router', array()));
And when i execute in my methodAction
$this->generateUrl('home');
he return me:
Catchable fatal error: Argument 1 passed to
Symfony\Component\Routing\Router::__construct() must be an instance of
Symfony\Component\Config\Loader\LoaderInterface, none given in
D:\xampp\htdocs\my_fw\vendor\symfony\routing\Router.php on line 95
looking on router constructor i see. I need that interface
public function __construct(LoaderInterface $loader, $resource, array $options = array(), RequestContext $context = null, LoggerInterface $logger = null)
how to avoid that implementation in service?
**New update:** routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
// Routing
$routes = new RouteCollection();
// Home
$routes->add('home', new Route('/', array(
'_controller' => 'MyCompany\\Controller\\HomeController::indexAction',
)));
You are having this error because you are configuring the router service with no service definition the service definition should have your service arguments which is the source of your error because the container try to create the router with no arguments check this
For better use you can configure the router service in the service.yml/.xml file
Edit: The documentation page for the Routing component has detailed setup instructions
Try injecting the router_loader service as an argument into the router service. For this case you have to use the Symfony\Component\DependencyInjection\Reference class.
Routes have to be configured with a config file when using the all in one Router class. You have to use a FileLocator and a real Loader class, like the YamlFileLoader, you can't just use the interface (services generally can't be interfaces in Symfony).
The service container setup for the router service should look like this:
use Symfony\Component\DependencyInjection\Reference;
// Loads config files from the current directory, change this to
// your liking, or add more than one path
$container->setDefinition('router_config_locator', new Definition(
'Symfony\Component\Config\FileLocator', [[__DIR__]]
));
$container->setDefinition('router_loader', new Definition(
'Symfony\Component\DependencyInjection\Loader\YamlFileLoader', [
new Reference('router_config_locator'),
]
));
$container->setDefinition('router',
new Definition('Symfony\Component\Routing\Router', array(
'loader' => new Reference('router_loader'),
// Definition of routes in Yaml form
'resource' => 'routes.yml',
))
);
The routes.yml file contains your route definitions:
home:
path: /home
defaults: {_controller: "MyController:index"}
Also have a look at this documentation page about setting up the routing component, which also talks about setting up the routing without the all in one class and config file.
I am looking for a good solution to on-the-fly connection of databases within Symfony utilizing Doctrine for entity management.
The scenario I have is that all inbound users to our service will be visiting *.website.com addresses, like client1.website.com.
We would like to use one Doctrine entity for the Client table to then look up their database credentials based on the URL of their account on the fly.
So far I have found the following topics here on stackoverflow that discuss dynamically changing the database credentials--but no clear workable solutions.
I'd like to propose collaborating to put together a working solution, and I'll put together a blog/tutorial post for other folks looking to modify database connection parameters within Symfony.
Here are some related posts:
Dynamic database connection symfony2
Symfony2, Dynamic DB Connection/Early override of Doctrine Service
Thanks!
If $em is existing entity manager and you want to reuse it's configuration, you can use this:
$conn = array(
'driver' => 'pdo_mysql',
'user' => 'root',
'password' => '',
'dbname' => 'foo'
);
$new = \Doctrine\ORM\EntityManager::create(
$conn,
$em->getConfiguration(),
$em->getEventManager()
);
I needed to do something similar - runtime discovery of an available database server. I did it by overriding the doctrine.dbal.connection_factory.class parameter and substituting my own derivation of the Doctrine bundle's ConnectionFactory class
My services.yml provides the parameter, pointing at my custom class
parameters:
doctrine.dbal.connection_factory.class: Path\To\Class\CustomConnectionFactory
Then fill in your discovery logic in Path\To\Class\CustomConnectionFactory.php
<?php
namespace Path\To\Class;
use Doctrine\Bundle\DoctrineBundle\ConnectionFactory;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
class CustomConnectionFactory extends ConnectionFactory
{
public function createConnection(array $params, Configuration $config = null, EventManager $eventManager = null, array $mappingTypes = array())
{
// Discover and override $params array here.
// A real-world example might obtain them from zookeeper,
// consul or etcd for example. You'll probably want to cache
// anything you obtain from such a service too.
$params['driver'] = 'pdo_mysql';
$params['host'] = '10.1.2.3';
$params['port'] = 3306;
$params['dbname'] = 'foo';
$params['user'] = 'myuser';
$params['password'] = 'mypass';
//continue with regular connection creation using new params
return parent::createConnection($params, $config, $eventManager,$mappingTypes);
}
}
Note also that Symfony 3.2 features the ability to use environment variables in container configurations, and to use their values on-demand (rather than fixing them when the container is compiled). See the blog announcement for more details.
I have a problem with execution of the Command in the Symfony2, mainly I have a command 'php app/console lets:do:this
which is executing the existing commands:
$this->dumpToXml($input, $output);
$this->dumpToAnnotation($input, $output);
$this->dumpToEntity($input, $output);
But the second command 'dumpToAnnotation' fails. It indeed generates the php entity files but without the namespace (the namespace is included in the orm.xml files).
That is the method that fails:
private function dumpToAnnotation(InputInterface $input, OutputInterface $output)
{
// check if the command exists
$command = $this->getApplication()->find('doctrine:mapping:convert');
// collect all arguments and options
$arguments = array(
'command' => 'doctrine:mapping:convert',
'to-type' => 'annotation',
'dest-path' => './src',
'--force' => true
);
// core objects
$input = new ArrayInput($arguments);
// execute the command
$command->run($input, $output);
}
It gives the output of Processing entity 'Users' instead of Processing entity 'MyApp/MyBundle/Entity/Users.
When I execute the command from the CLI, there is no problem at all, it's just does not work in the Command of Symfony2.
I hope someone can help me with that.
Regards, Greg.
Disclaimer: I'm slowly starting to get into Symfony and still have some problems understanding how the architecture works.
Currently I set up different Bundles (Services, right?) that should deliver different output for different routes. So far I got around adding a simple Twig template that loads stylesheets and scripts via Assetics and Twig-blocks. Now I added another Bundle that queries data via Buzz from a remote location, which worked fine as a standalone script, but I don't get around printing output in a Twig template.
The architecture of the original script is like the following (names made more generic):
Vendors - abstract class that serves as base for all remote request Bundles.
ServiceABC - abstract class that extends Vendors and defines Error handling and output preparation for the ABC service.
ClientXYZ - final class that extends Service_ABC, defines output parsing and normalization of the returned data.
This Bundle got a services.yml file:
# ~/MyApp/Bundle/ServiceABCBundle/Resources/config/services.yml
parameters:
service_abc_manager.class: MyApp\Bundle\ServiceABCBundle\Models\Service_ABC
location_manager.class: MyApp\Bundle\ServiceABCBundle\Models\Clients\ClientLocation
monitor_manager.class: MyApp\Bundle\ServiceABCBundle\Models\Clients\ClientMonitor
services:
service_abc_manager:
abstract: true
location_manager:
class: %location_manager.class%
parent: service_abc_manager
monitor_manager:
class: %monitor_manager.class%
parent: service_abc_manager
Names changed for easier reference - Typos by accident possible.
Now my problem/question is, that I don't really get behind the Symfony2 concept of how to get the output into the template.
namespace MyApp\Bundle\ServiceABCBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use MyApp\Bundle\ServiceABCBundle\Models\Clients\ClientLocation;
class DefaultController extends Controller
{
public function indexAction()
{
$services = array();
$services[] = $this->container->has('service_abc_manager');
$services[] = $this->container->has('location_manager');
$services[] = $this->container->has('client_location');
$services[] = $this->container->has('ClientLocation');
var_dump( $services );
$client = new ClientLocation();
var_dump( $client );
$response = $this->render(
'Service_ABC:Default:index.html.twig'
);
# $response->setCharset( 'utf-8' );
# $response->headers->set( 'Content-Type', 'text/html' );
return $response;
}
}
The output of the first array() named $services is always false and the $client = new ClientLocation(); throws an Exception that the class name wasn't found.
How can I access those Services/Bundle(parts)/Classes? And how would I render the output to a template?
Update
After I added the complete tree definition to Configuration()->getConfigTreeBuilder(), I'm able to see the definitions in the CLI:
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root( 'myapp_service_abc' );
$rootNode
->children()
->scalarNode('service_abc_manager')->end()
->scalarNode('location_manager')->end()
->scalarNode('monitor_manager')->end()
->end()
;
return $treeBuilder;
}
}
The CLI command php app/console config:dump-reference myapp_service_abc now gives me the following output:
myapp_service_abc:
service_abc_manager: ~
location_manager: ~
monitor_manager: ~
I can as well see that the config data was loaded, when I var_dump( $loader ); inside MyAppServiceABCExtension right after $loader->load( 'services.yml' ); was called.
The output is the following:
object(Symfony\Component\DependencyInjection\Loader\YamlFileLoader)
protected 'container' =>
object(Symfony\Component\DependencyInjection\ContainerBuilder)
private 'definitions' =>
array
'service_abc_manager' =>
object(Symfony\Component\DependencyInjection\Definition)
'location_manager' =>
object(Symfony\Component\DependencyInjection\DefinitionDecorator)
private 'parent' => string 'service_abc_manager'
// etc.
The problem itself remains: There's still a FALSE return value inside DefaultController()->indexAction() when I var_dump( $this->container->has( 'service_abc_manager' );. I as well tried var_dump( $this->container->has( 'location_manager' ); and var_dump( $this->container->has( 'myapp.service_abc_manager' ); with the same result.
You should not call your services from the twig file, but from the controller.
The role of the controller is to :
validate your forms if there were a form posted
call your services to get some stuffs to display in a view
initialize forms if there is a form to display
return a Response that typically contains a rendered twig view
Do not call your services using something like $client = new ClientLocation();, but call it using the service container. This will allow you to take the whole power of the dependancy injection offered by Symfony2.
Your controller will look like :
<?php
namespace MyApp\Bundle\ServiceABCBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
$locationService = $this->container->get('location_manager');
$someStuffs = $locationService->someMethod();
$response = $this->render(
'ServiceABCBundle:Default:index.html.twig', array('stuffs' => $someStuffs)
);
return $response;
}
}
From your twig file, you'll be able to use the stuffs variable :
{{ stuffs }} if your variable is a terminal ( a string, a number... )
{{ stuffs.attribute }} if your variable is an object or an array
About your services file, I am a bit confused, because your architecture does not look to be the standard Symfony2's one :
# ~/MyApp/Bundle/ServiceABCBundle/Resources/config/services.yml
Why your services.yml file isn't in the src/MyApp/SomethingBundle/Resources/config/ directory?
If you didn't already read it, I suggest you to have a look to the Symfony2 : The Big Picture documentation, which is the best way to start with Symfony2.