I am upgrading our Symfony application from version 3.4 to version 4.4, which includes an upgrade of Doctrine from 1.12 to 2.3. I had previously written a class that modified the results to the doctrine:schema:update command, which worked great, but appears not to be working now. The class is below.
To modify doctrine:schema:update, I created a class called DoctrineUpdateCommand, which extended \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand, and placed it in the Command folder of the bundle. This was all that was needed. I referenced this answer to figure out how to do it: How to set up entity (doctrine) for database view in Symfony 2.
We need to override the doctrine:schema:update command because one of our entities refers to a MySQL view instead of a MySQL table. Further, the entity is referenced as both a stand alone entity, and as a many-to-many join. The override class caused the entity and join to be ignored. It also added a sql statement to create the view.
After the upgrade, if I run php console doctrine:schema:update --dump-sql, I get these results:
19:08:16 CRITICAL [console] Error thrown while running command "doctrine:schema:update --dump-sql". Message: "The table with name 'nest_qa.assignedrole_privilegerole' already exists." ["exception" => Doctrine\DBAL\Schema\SchemaException^ { …},"command" => "doctrine:schema:update --dump-sql","message" => "The table with name 'nest_qa.assignedrole_privilegerole' already exists."]
In SchemaException.php line 112:
The table with name 'nest_qa.assignedrole_privilegerole' already exists.
I am fairly certain that the extension of the doctrine command is no longer being called, and it's using the default command instead, but I can't figure out how to change that. Any help would be appreciated.
Here is the original class:
<?php
namespace ApiBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Doctrine\ORM\Tools\SchemaTool;
class DoctrineUpdateCommand extends \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand{
protected $ignoredEntities = array(
'ApiBundle\Entity\AssignedrolePrivilegerole',
);
protected $ignoreAssociationMappings = array(
'ApiBundle\Entity\Privilegerole' => 'assignedroles',
'ApiBundle\Entity\Assignedrole' => 'privilegeroles',
);
protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui) {
/** #var $metadata \Doctrine\ORM\Mapping\ClassMetadata */
$newMetadatas = array();
foreach ($metadatas as $metadata) {
if (array_key_exists($metadata->getName(), $this->ignoreAssociationMappings)) {
if(array_key_exists($this->ignoreAssociationMappings[$metadata->getName()], $metadata->getAssociationMappings())){
unset($metadata->associationMappings[$this->ignoreAssociationMappings[$metadata->getName()]]);
}
}
//If metadata element is not in the ignore array, add it to the new metadata array
if (!in_array($metadata->getName(), $this->ignoredEntities)){
array_push($newMetadatas, $metadata);
}
}
parent::executeSchemaCommand($input, $output, $schemaTool, $newMetadatas, $ui);
$output->writeln("------Create view for assignedrole_privilegerole");
$output->writeln("CREATE VIEW `assignedrole_privilegerole` AS select `Assignedrole`.`id` AS `assignedrole_id`,`Privilegerole`.`id` AS `privilegerole_id` from (`Assignedrole` join `Privilegerole`) where ((`Assignedrole`.`role_id` = `Privilegerole`.`role_id`) and ((`Assignedrole`.`unit_id` = `Privilegerole`.`unit_id`) or `Privilegerole`.`unit_id` in (select `Unittree`.`ancestor_id` from `Unittree` where (`Unittree`.`descendant_id` = `Assignedrole`.`unit_id`)))) ;");
}
}
Console commands in symfony <4.x were registered by scanning Command folder inside a bundle. Since bundles are obsolete in symfony 4+, you have to define commands in your services definition by tagging the command class, or use DI autoconfiguration.
Option 1: explicitly add console.command tag to the service definition:
services:
ApiBundle\Command\DoctrineUpdateCommand:
tags:
- { name: twig.extension }
Option 2: use DI autoconfiguration:
services:
ApiBundle\Command\DoctrineUpdateCommand:
autoconfigure: true
After your class is registered as a console command, it must override the default one.
See symfony docs for more: Console Command
Related
In a Symfony 2.7 app, we have attempted to set up a humanize_bytes Twig filter in order to convert long numbers of bytes into human-readable form -- 10 MB, for example.
Within our HumanReadableBytesExtension.php file is the following:
public function getFilters() {
return [
new TwigFilter('humanize_bytes', [$this, 'getHumanReadableBytesFilter'])
];
}
... and in our services.yml file lies the following:
mycompany.cms.twig.extension.human_readable_bytes_extension:
class: MyCompany\TwigExtensions\HumanReadableBytesExtension
arguments:
- '#translator'
tags:
- {name: twig.extension}
... but we find that the getFilters() method is not getting called, and that when we try to call the filter in a Twig template, we get:
Unknown "humanize_bytes" filter.
Both files pass syntax validation. The cache has been cleared. Is there somewhere else where we should be registering this filter?
====
Edit: Here is the output of the app/console debug:container mycompany.cms.twig.extension.human_readable_bytes_extension command:
[container] Information for service
mycompany.cms.twig.extension.human_readable_bytes_extension Service Id
mycompany.cms.twig.extension.human_readable_bytes_extension Class
MyCompany\TwigExtensions\HumanReadableBytesExtension Tags
- twig.extension () Scope container Public yes Synthetic no Lazy no
Synchronized no Abstract no
You mentioned you are using an abstract class. Did you override the getName method in your HumanReadableBytesExtension ?
If two extensions have the same name, only one will be loaded, the second will be silently ignored.
I ultimately just took all of my changes and put them on a fresh feature branch. That "fixed" the problem, albeit in a very non-satisfactory way. (We never really did figure out what was going wrong.)
I've inherited a Symfony 3 project that had no migrations.
Following the documentation, I added Doctrine Migrations Bundle to Symfony.
$ composer require doctrine/doctrine-migrations-bundle "^1.0"
Added the configuration to config.yml
doctrine_migrations:
dir_name: "%kernel.root_dir%/DoctrineMigrations"
namespace: Application\Migrations
table_name: migration_versions
name: Application Migrations
Then added the loader to appKernel.php
new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
The console now shows doctrine:migrations:* as available features, so I then generated my migration file
php bin/console doctrine:migrations:generate
Generated new migration class to "/path/to/project/app/DoctrineMigrations/Version20100621140655.php"
Then continued following along with the Doctrine migrations documentation:
public function up(Schema $schema)
{
// alter my table
$options = ['fields' => ['type_id']];
$schema->addIndex('advertiser', 'idx_type_id', $options);
...
}
Now, when I run the migration:
Migrating up to 20170714165306 from 20170714155549
++ migrating 20170714165306
[Symfony\Component\Debug\Exception\UndefinedMethodException]
Attempted to call an undefined method named "addIndex" of class "ProxyManagerGeneratedProxy\__PM__\Doctrine\DBAL\Schema\Schema\Generatedaca9b50393335f8354881e485c485329".
Now when I got that error, I searched on that and found ocramius/ProxyManager. I installed that with
composer require ocramius/proxy-manager "^2.0"
Still got the same error.
Granted, in most of the Doctrine Migrations documentation, I see mainly addSql( 'ALTER TABLE ...') type statements, but I want to use the wrapper methods addIndex, removeIndex, etc.
Can someone explain how to get these features working?
I figured out my problem. The problem is solved by doing the following in the migration file:
public function up(Schema $schema)
{
// alter my table
$table = $schema->getTable('advertiser');
$table->addIndex(['type_id'], 'idx_type_id');
...
}
The basic idea is that you need to grab a reference to the table, and then you can addIndex to the specific column on the table. Contrary to the Doctrine docs, you can't addIndex on a $schema reference passing the table name within a Symfony2/3 migration.
I created file commands/TestCommand.php in my yii-powered project:
class TestCommand extends CConsoleCommand
{
public function actionIndex()
{
echo "Hello World!\n";
}
}
And it's became visible via yiic:
Yii command runner (based on Yii v1.1.14)
Usage: yiic.php <command-name> [parameters...]
The following commands are available:
- message
- migrate
- shell
- test <<<
- webapp
To see individual command help, use the following:
yiic.php help <command-name>
If I am trying to get some help information about this console command:
php yiic.php help test
I see default text:
Usage: yiic.php test index
How can I write my TestCommand class which will show my help information? Is it a some public field or special method which return help text? I need something like that:
php yiic.php help webapp
Result like I need:
USAGE
yiic webapp <app-path> [<vcs>]
DESCRIPTION
This command generates an Yii Web Application at the specified location.
PARAMETERS
* app-path: required, the directory where the new application will be created.
If the directory does not exist, it will be created. After the application
is created, please make sure the directory can be accessed by Web users.
* vcs: optional, version control system you're going to use in the new project.
Application generator will create all needed files to the specified VCS
(such as .gitignore, .gitkeep, etc.). Possible values: git, hg. Do not
use this argument if you're going to create VCS files yourself.
You can override the default getHelp method to implements your help!
It must return a string that is the help text.
Provides the command description. This method may be overridden to return the actual command description.
Here is the default method:
public function getHelp()
{
$help='Usage: '.$this->getCommandRunner()->getScriptName().' '.$this->getName();
$options=$this->getOptionHelp();
if(empty($options))
return $help."\n";
if(count($options)===1)
return $help.' '.$options[0]."\n";
$help.=" <action>\nActions:\n";
foreach($options as $option)
$help.=' '.$option."\n";
return $help;
}
You can also override the default getOptionHelp method that is called in getHelp
Provides the command option help information. The default implementation will return all available actions together with their corresponding option information.
Source
I have defined some fixtures in doctrine.
When i try to run using this
php app/console doctrine:fixtures:load then it asks me to purge the database.
is it possible to load it without purging database.
I remeber Django has fixtures which can be loaded in separate tables without purging existing database
Use the --append option
php app/console help doctrine:fixtures:load
Usage:
doctrine:fixtures:load [--fixtures[="..."]] [--append] [--em="..."] [--purge-with-truncate]
Options:
--fixtures The directory or file to load data fixtures from. (multiple values allowed)
--append Append the data fixtures instead of deleting all data from the database first.
--em The entity manager to use for this command.
--purge-with-truncate Purge data by using a database-level TRUNCATE statement
DoctrineFixtures are nice for the first init of an empty database or in development - but not in production.
Take a look at DoctrineMigrations and symfonys DcotrineMigrationsBundle, this is a good and safe way to migrate your database in production.
It is possible to update previously added data to DB (which was loaded by running app/console doctrine:fixtures:load). I used EntityManager->createQuery for that.
But I still wonder if there is or ever will be an opportunity to do it by simply running an app/console command. Something like app/console doctrine:schema:update --force, but applied to the data itself.
For now I had to write a ton of code to get my data updated and appended properly. For example to make --append work I had to write the following:
class LoadCategoryData implements FixtureInterface
{
/**
* {#inheritDoc}
*/
public function load(ObjectManager $em)
{
//a category has name and shortcut name (st)
$categories = array (
[0]=> array('name' => 'foo', 'st' = 'bar'),
...
);
foreach ($categories as $cat) {
$query = $em->createQuery(
"SELECT c
FROM MyBundle:Category c
WHERE c.st = ?1"
)->setParameter(1, $cat['st'])->getResult();
if (count($query) == 0) {
$c = new Category();
$c->setName($cat['name']);
$c->setSt($cat['st']);
$em->persist($c);
}
$em->flush();
}
}
}
We are creating a command that relies on other commands to generate a new database and build out its schema. So far we have successfully gotten it to read the config.yml file, add our new connection information, and to write the file back. In the same command we are then trying to run the symfony commands to create the database and schema:update. This is where we are running into problems. We get the following error:
[InvalidArgumentException] Doctrine ORM Manager named "mynewdatabase"
does not exist.
If we run the command a second time there is no error because the updated configuration file is loaded fresh into the application. If we manually run the doctrine commands after writing to the config.yml file it also works without error.
We are thinking that at the point in our command where we're running the database create and update commands, it's still using the current kernel's version of the config.yml/database.yml that are stored in memory. We have tried a number of different ways to reinitialize the application/kernel configuration (calling shutdown(), boot(), etc) without luck. Here's the code:
namespace Test\MyBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;
class GeneratorCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('generate')
->setDescription('Create a new database.')
->addArgument('dbname', InputArgument::REQUIRED, 'The db name')
;
}
/*
example: php app/console generate mynewdatabase
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
//Without this, the doctrine commands will prematurely end execution
$this->getApplication()->setAutoExit(false);
//Open up app/config/config.yml
$yaml = Yaml::parse(file_get_contents($this->getContainer()->get('kernel')->getRootDir() .'/config/config.yml'));
//Take input dbname and use it to name the database
$db_name = $input->getArgument('dbname');
//Add that connection to app/config/config.yml
$yaml['doctrine']['dbal']['connections'][$site_name] = Array('driver' => '%database_driver%', 'host' => '%database_host%', 'port' => '%database_port%', 'dbname' => $site_name, 'user' => '%database_user%', 'password' => '%database_password%', 'charset' => 'UTF8');
$yaml['doctrine']['orm']['entity_managers'][$site_name] = Array('connection' => $site_name, 'mappings' => Array('MyCustomerBundle' => null));
//Now put it back
$new_yaml = Yaml::dump($yaml, 5);
file_put_contents($this->getContainer()->get('kernel')->getRootDir() .'/config/config.yml', $new_yaml);
/* http://symfony.com/doc/current/components/console/introduction.html#calling-an-existing-command */
//Set up our db create script arguments
$args = array(
'command' => 'doctrine:database:create',
'--connection' => $site_name,
);
$db_create_input = new ArrayInput($args);
//Run the symfony database create arguments
$this->getApplication()->run($db_create_input, $output);
//Set up our schema update script arguments
$args = array(
'command' => 'doctrine:schema:update',
'--em' => $site_name,
'--force' => true
);
$update_schema_input = new ArrayInput($args);
//Run the symfony database create command
$this->getApplication()->run($update_schema_input, $output);
}
}
The reason this doesn't work is because the DIC goes through a compilation process and is then written to a PHP file which is then included into the current running process. Which you can see here:
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/Kernel.php#L562
If you change the service definitions and then try to "reboot" the kernel to compile these changes it won't include the compiled file a second time (require_once) and it will just create another instance of the already included DIC class with the old compiled service definitions.
The simplest way I can think of to get around this is to create an empty Kernel class that simply extends your AppKernel. Like so:
<?php
namespace Test\MyBundle\Command;
class FakeKernel extends \AppKernel
{
}
Then in your command, you can boot up this kernel after you've saved the new service definitions and it will re-compile a new DIC class using the "FakeKernel" name as part of the file name which means it will be included. like so:
$kernel = new \Test\MyBundle\Command\FakeKernel($input->getOption('env'), true);
$application = new \Symfony\Bundle\FrameworkBundle\Console\Application($kernel);
Then you run your sub-commands against this new application which will be running with the new DIC:
$application->run($db_create_input, $output);
disclaimer: this feels very hacky. I'm open to hearing other solutions/workarounds.