I want to integrate Doctrine ORM into my (non-Symfony) project. I already done this in another project and used the famous cli-config.php into the project root directory.
But now, in my new project, I use the Symfony Console component and the Dependency Injection component (to reference services and commands, by tagging them).
1. I absolutely don't want to have a cli-config.php in the project root. How the Sf Doctrine Bundle do this?
2. Also (but it is less important), I would like to have the Doctrine commands into my project CLI.
What would be the best way to do this? Create references to Doctrine commands into my services.yml ? Or create local "decorator commands" that call Doctrine commands via PHP?
Finally, after some googling and experiments, I found a complete solution.
Just read the doctrine.php in vendor/bin. It is very easy to avoid the hardcoded config-cli.php file.
1. Create an entity manager
In my case, I use a factory and this method hydrates the doctrine.em service.
($config is specific to my app, change values to use your own logic.)
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
public function createEntityManager()
{
$config = $this->get('config');
$metadataConfig = Setup::createAnnotationMetadataConfiguration(
[$config->meta('app_path') . '/Entity'],
$config->oc->doctrine->dev_mode
);
return EntityManager::create((array) $config->oc->doctrine->connection, $metadataConfig);
}
2. Merge Doctrine CLI commands in your CLI commands
Somewere in your code, like in some bootstrap.php, you probably declare your Symfony\Component\Console\Application command line interface, that's how I do this (the foreach simply adds commands defined in my services.yml file):
$application = new Application('MyApp CLI', '0.0.1');
$services = $container->findTaggedServiceIds('oc.command');
foreach(array_keys($services) as $serviceId)
{
$application->add($container->get($serviceId));
}
$application->run();
Now, we simply ask Doctrine to inject its commands into our Application:
$application = new Application('MyApp CLI', '0.0.1');
$helperSet = ConsoleRunner::createHelperSet($container->get('doctrine.em'));
$application->setHelperSet($helperSet);
ConsoleRunner::addCommands($application);
$services = $container->findTaggedServiceIds('oc.command');
foreach(array_keys($services) as $serviceId)
{
$application->add($container->get($serviceId));
}
$application->run();
That's it! You can also only add a subset of the Doctrine commands by using arsfeld's answer on this GitHub issue.
3. Bonus: only import needed commands and rename them
You can create decorator commands that inherit Doctrine commands (this is useful to redefine the name of Doctrine commands, as Symfony Doctrine Bundle does, eg. orm:validate-schema -> doctrine:schema:validate).
To do this, remove the line ConsoleRunner::addCommands($application); we added in step 2. For each command you want to redefine, you will need to create an register a new command in your app. This command will "extends" the target Doctrine command and will override the configure() method.
Here is an example with orm:validate-schema:
<?php
namespace MyApp\Command\Doctrine;
use Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand;
class SchemaValidateCommand extends ValidateSchemaCommand
{
protected function configure()
{
parent::configure();
$this->setName('doctrine:schema:validate');
}
}
Some Doctrine commands have aliases that will pollute your command namespaces, like orm:generate-entities and orm:generate:entities.
To remove these aliases, in configure(), add ->setAliases(array()).
$this->setName('doctrine:generate:entities')->setAliases([]);
Congratulations, you just redone the Symfony Doctrine Bundle :p (jk)
Related
How to extend generated command in Symfony with use Akeneo\Pim\AkeneoPimClientInterface ?
I have generated a command using php app/console generate:command and I got this class:
class AppTriggerBuildCommand extends ContainerAwareCommand
Then developed it to the point when I need all the categories from the API. Seamlessly it is really an easy question, how can I use AkeneoPimClientInterface in the command.
I want to use it something like this.
$categories = $this->apiClient->getCategoryApi()->all();
And the apiClient in here comes inside the _contruct metod
public function __construct(AkeneoPimClient $apiClient, AkeneoLocaleMapper $mapper) {
$this->apiClient = $apiClient;
$this->mapper = $mapper;
}
And in use
use Akeneo\Pim\AkeneoPimClientInterface as AkeneoPimClient;
But when I tried to put it inside the _construct method in a command it want to use the parent _construct and it just can't see the generated command.
Could anyone help me ?
php app/console trigger build -vvv
[Symfony\Component\Console\Exception\CommandNotFoundException]
Command "trigger" is not defined.
Exception trace:
() at /var/www/html/iclei/vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:526
Symfony\Component\Console\Application->find() at /var/www/html/iclei/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Console/Application.php:94
Symfony\Bundle\FrameworkBundle\Console\Application->find() at /var/www/html/iclei/vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:190
Symfony\Component\Console\Application->doRun() at /var/www/html/iclei/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Console/Application.php:84
Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /var/www/html/iclei/vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:117
Symfony\Component\Console\Application->run() at /var/www/html/iclei/app/console:27
Since you extend the ContainerAwareCommand you have access to Symfony's Service Container to fetch the client like this (you might have to replace the fully qualified class name with a service id, I'm not 100% sure):
$this->container->get('Akeneo\Pim\AkeneoPimClientInterface');
If you want to use the constructor (which I encourage you to do), you are almost there. You just have to also call the parent constructor as well:
public function __construct(AkeneoPimClient $apiClient, AkeneoLocaleMapper $mapper)
{
parent::__construct();
$this->apiClient = $apiClient;
$this->mapper = $mapper;
}
Both ways should work, but the latter allows you to move away from the ContainerAwareCommand to the more generic ContainerCommand, which will help you with Symfony 4 where services in the container will be private by default and therefore you can not just simply get them from the container like in the first version.
edit regarding the command name: You can specify the name of your command as argument to parent::__construct() and also set it via the configure() method, you need to override. In there you can just call, e.g. $this->setName('trigger-build');. Be careful not to use spaces, as Symfony will treat those as separate arguments. So trigger is the name of the command and build is the first argument you "feed" to the command.
What I have is a symfony application, which contains some entities along with some repositories. A second non-symfony application should interface with the first one for interacting with some logic written in it (in this very moment just using the entities and their proper repositories).
Keep in mind that the first application could have its own autoload register etc.
I thought of an API class for external applications, which stays in the app directory. To use that the application should require a script. Here is the idea:
app/authInterface.php that the external application should require:
$loader = require __DIR__.'/autoload.php';
require_once (__DIR__.'/APIAuth.php');
return new APIAuth();
and an example of a working APIAuth I wrote (the code is kind of messy: remember this is just a try but you can get the idea):
class APIAuth
{
public function __construct()
{
//dev_local is a personal configuration I'm using.
$kernel = new AppKernel('dev_local', false);
$kernel->loadClassCache();
$kernel->boot();
$doctrine = $kernel->getContainer()->get('doctrine');
$em = $doctrine->getManager();
$users = $em->getRepository('BelkaTestBundle:User')->findUsersStartingWith('thisisatry');
}
by calling it by the shell everything works and I'm happy with it:
php app/authInterface.php
but I'm wondering if I'm doing in the best way possible in terms of:
resources am I loading just the resources I really need to run my code? Do I really need the kernel? That way everything is properly loaded - including the DB connection- but I'm not that sure if there are other ways to do it lighter
symfony logics am I interacting with symfony the right way? Are there better ways?
Symfony allows using its features from the command line. If you use a CronJob or another application, and want to call your Symfony application, you have two general options:
Generating HTTP endpoints in your Symfony application
Generating a command which executes code in your Symfony application
Both options will be discussed below.
HTTP endpoint (REST API)
Create a route in your routing configuration to route a HTTP request to a Controller/Action.
# app/config/routing.yml
test_api:
path: /test/api/v1/{api_key}
defaults: { _controller: AppBundle:Api:test }
which will call the ApiController::testAction method.
Then, implement the testAction with your code you want to excecute:
use Symfony\Component\HttpFoundation\Response;
public function testAction() {
return new Response('Successful!');
}
Command
Create a command line command which does something in your application. This can be used to execute code which can use any Symfony service you have defined in your (web)application.
It might look like:
// src/AppBundle/Command/TestCommand.php
namespace AppBundle\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\Output\OutputInterface;
class GreetCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('myapp:section:thecommand')
->setDescription('Test command')
->addArgument(
'optionname',
InputArgument::OPTIONAL,
'Test option'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$option = $input->getArgument('optionname');
if ($option) {
$text = 'Test '.$option;
} else {
$text = 'Test!';
}
$output->writeln($text);
}
}
Look here for documentation.
Call your command using something like
bin/console myapp:section:thecommand --optionname optionvalue
(Use app/console for pre-3.0 Symfony installations.)
Use whichever option you think is best.
One word of advice. Do not try to use parts of the Symfony framework when your application is using the full Symfony framework. Most likely you will walk into trouble along the way and you're making your own life hard.
Use the beautiful tools you have at your disposal when you are already using Symfony to build your application.
In a Symfony2 project, when you use a Controller, you can access Doctrine by calling getDoctrine() on this, i.e.:
$this->getDoctrine();
In this way, I can access the repository of such a Doctrine Entity.
Suppose to have a generic PHP class in a Symfony2 project. How can I retrieve Doctrine ?
I suppose that there is such a service to get it, but I don't know which one.
You can register this class as a service and inject whatever other services into it. Suppose you have GenericClass.php as follows:
class GenericClass
{
public function __construct()
{
// some cool stuff
}
}
You can register it as service (in your bundle's Resources/config/service.yml|xml usually) and inject Doctrine's entity manager into it:
services:
my_mailer:
class: Path/To/GenericClass
arguments: [doctrine.orm.entity_manager]
And it'll try to inject entity manager to (by default) constructor of GenericClass. So you just have to add argument for it:
public function __construct($entityManager)
{
// do something awesome with entity manager
}
If you are not sure what services are available in your application's DI container, you can find out by using command line tool: php app/console container:debug and it'll list all available services along with their aliases and classes.
After checking the symfony2 docs i figured out how to pass your service
in a custom method to break the default behavior.
Rewrite your configs like this:
services:
my_mailer:
class: Path/To/GenericClass
calls:
- [anotherMethodName, [doctrine.orm.entity_manager]]
So, the Service is now available in your other method.
public function anotherMethodName($entityManager)
{
// your magic
}
The Answer from Ondrej is absolutely correct, I just wanted to add this piece of the puzzle to this thread.
I have the bundle which is called the Linked Bundle. Now I have put all the mix stuff in there which is used across all bundles.
But I don't have any entity called Linked.
I want to create LinkedRepository so that i can have my all common function there. But how will i get that repository in other bundles. I mean how to call this
$repository = $em->getRepository('LinkedBundle:"*What should I write here*"');
You cannot have a separate repository class. A repository class is linked to an entity, so you cannot have a "standalone" repository but I can see two options:
Subclass EntityRepository and name it LinkedRepository, here you can add your common methods. All your custom Repository class will have to subclass your LinkedRepository class. If you want that common functionality in all your Entity's Repository instances but you don't need a custom Repository class you can declare the LinkedRepository class as the Entity's repositoryClass #ORM\Entity(repositoryClass="Acme\LinkedBundle\Repository\LinkedRepository") assuming you have your Repository classes in the Repository folder inside your Bundle and replace Acme with your company name.
Create a service and add your common functionality in there.
I guess the first one is easier.
i think this it is not possible as you intend to to it. But I would recommend using a Service Container instead of a Repository. In this Service Container you can use different repositories, which you need to use for these global tasks. The Service Container is also accessible in every controller etc..
Here is the Documentatino for it: http://symfony.com/doc/current/book/service_container.html
I don't think that you need the whole injection part. Just define an Service:
services:
linked_service:
class: Acme\LinkedBundleBundle\Service\LinkedService
And then get the service in your controller via
public function indexAction()
{
$service = $this->get('linked_service');
}
Hope this works.
I am looking at doctrine2 and how to handle data fixtures. I am especially interested in reading them from flat files (csv, yaml, xls).
In doctrine 1.2 data fixtures are handled like this: http://www.doctrine-project.org/projects/orm/1.2/docs/manual/data-fixtures/en#data-fixtures
Any suggestion how to handle this in doctrine2?
As already mentioned by Steven the fixture-feature comes as a separate repo.
It took me some time to figure out how to install the data fixtures feature in Symfony2, so here is how I did it:
add sources to your deps file:
[doctrine-fixtures]
git=http://github.com/doctrine/data-fixtures.git
[DoctrineFixturesBundle]
git=http://github.com/symfony/DoctrineFixturesBundle.git
target=/bundles/Symfony/Bundle/DoctrineFixturesBundle
update your vendors
$ php bin/vendors install
register in in autoload.php:
$loader->registerNamespaces(array(
//...
'Doctrine\\Common\\DataFixtures' => __DIR__.'/../vendor/doctrine-fixtures/lib',
'Doctrine\\Common' => __DIR__.'/../vendor/doctrine-common/lib',
//..
));
add class which subclasses FixtureInterface:
<?php
use Doctrine\ORM\EntityManager,
Doctrine\Common\DataFixtures\FixtureInterface;
/**
*
* setup of initial data for the unit- and functional tests
* #author stephan
*/
class LoadTestingData implements FixtureInterface{
/**
*
* #param EntityManager $manager
*/
public function load($manager) {
$user = new User();
$user->setUsername("testuser");
$manager->persist($user);
}
//...
load data fixtures via console command
./app/console doctrine:data:load
There is a git submodule for that in the official doctrine git repo https://github.com/doctrine/data-fixtures
I am currently using it and it works pretty well.
I use class-based fixtures, much better this way because you can handle associations and dependencies easily with the EntityManager directly, also easy for using in unit tests.
Here is the library I use with Zend Framework modules, but you can just write your own loader. There is a command line script too.