Symfony 5 - One entity, one Repository and two Databases - doctrine-orm

I have a problem querying my database via Symfony Repository.
If I don't specify a repository
/**
* ProductsText
*
* #ORM\Table(name="products_text")
* #ORM\Entity
*/
i can select one of my databases by using Doctrine like this:
->getRepository(ProductsText::class, "db1").
But if i declare the repository:
/**
* ProductsText
*
* #ORM\Table(name="products_text")
* #ORM\Entity(repositoryClass="App\Repository\Product\ProductsTextRepository")
*/
It only selects the database from which I pulled the entity from.
I can't use two different entities because the user selects the "database" when logging in. Unfortunately, I can't merge the two databases yet either, as they are not multi-tenant.
My guess is that I can solve the problem by keeping the registry from the repository in general. My previous attempts unfortunately ended up with the front end no longer loading.
Works, but only for one database:
class ProductsTextRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ProductsText::class);
}
Did not works:
class TbProductsTextRepository
{
/**
* #var EntityRepository
*/
private $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository(TbProductsText::class);
}
Unfortunately, I don't understand the mechanics of how the repositorys are regestrated.
I use:
Symfony 5.2
Doctrine 2.2
Doctrine ORM 2.8
Doctrine Migrations Bundle 3.0

I solved the problem by not "registering" the repository.
By using EntityRepository and without a construct, I can use the repository for both databases.
use Doctrine\ORM\EntityRepository;
class TbProductsTextRepository extends EntityRepository
{

Related

Symfony2 phpunit tests failed with the error invalid mapping file in orm.yml

I am new in an project and we want to resume the stand of working still yet.
I found testfiles in project. These were a good point for me to have an entry and find out the things.
So let before say, this is a Symfony 2.8 project which uses fosuserbundle.
Back to my matter: when I execute phpunit in terminal so I get MappingExceptions from doctrine:
PHPUnit 5.1.3 by Sebastian Bergmann and contributors.
WWWWWWWWWWWWWWWWWWWWWWWWWWEWWWWWWEWWWWWWWWWWWWWWWWWWW 53 / 53 (100%)
Time: 1.4 seconds, Memory: 36.50Mb
There were 2 errors:
1) ******\*****Bundle\Tests\Controller\******ControllerTest::testIndex
Doctrine\Common\Persistence\Mapping\MappingException: Invalid mapping file '******.UserBundle.Entity.User.orm.yml' for class '*******\UserBundle\Entity\User'.
/var/www/*****vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/MappingException.php:86
/var/www/********/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileDriver.php:117
/var/www/********/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php:56
/var/www/********/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriverChain.php:102
/var/www/*******/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php:116
/var/www/*******/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php:332
/var/www/*******/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php:216
/var/www/********/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:265
/var/www/********/vendor/doctrine/orm/lib/Doctrine/ORM/Repository/DefaultRepositoryFactory.php:67
/var/www/********/vendor/doctrine/orm/lib/Doctrine/ORM/Repository/DefaultRepositoryFactory.php:50
/var/www/*******/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:665
/var/www/*******/vendor/friendsofsymfony/user-bundle/Doctrine/UserManager.php:40
It seems as something going wrong with User entity and related mapping file.
But these errors appear only when I execute phpunit. I deleted the user entity and orm file and run the commands generate entities and update schema. They work without any errors.
Here is the or.yml:
*****\UserBundle\Entity\User:
type: entity
table: fos_user
repositoryClass: ******\UserBundle\Entity\UserRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
confirm:
type: bigint
and here the entity class which extends from fos superclass user
<?php
namespace *****\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
*
*/
class User extends BaseUser
{
/**
* #var integer
*/
protected $confirm;
/**
* Set confirm
*
* #param integer $confirm
* #return User
*/
public function setConfirm($confirm)
{
$this->confirm = $confirm;
return $this;
}
/**
* Get confirm
*
* #return integer
*/
public function getConfirm()
{
return $this->confirm;
}
}
I am spending now the whole day with this issue but I can not figure out what is going wrong. It seems that for doctrine is the yml file invalid but why then it happens only with unittest?

Entity-class mapping invalid: inconsistent with each other

I have a table "cms_objects" // Object.php - that stores all object info
I have another table "cms_media" // Media.php - that stores all media info
An object can have many media items (post with lots of different images)
In Object.php
/**
* #ORM\OneToMany(targetEntity="Media", mappedBy="Object")
*/
private $cms_media;
In Media.php
/**
* #ORM\ManyToOne(targetEntity="Object", inversedBy="cms_media")
* #ORM\JoinColumn(name="object_id", referencedColumnName="id")
*
* #Annotation\Exclude()
*/
private $object;
When I run: php public/index.php orm:validate-schema - I get:
[Mapping] FAIL - The entity-class 'Application\Entity\Cms\Media' mapping is invalid:
* The mappings Application\Entity\Cms\Media#object and Application\Entity\Cms\Object#cms_media are inconsistent with each other.
[Mapping] FAIL - The entity-class 'Application\Entity\Cms\Object' mapping is invalid:
* The association Application\Entity\Cms\Object#cms_media refers to the owning side field Application\Entity\Cms\Media#Object which does not exist.
Ideally, I need to be able to create a ZF2 form with element: 'media' or 'cms_media' but I haven't been able to validate it yet...
You can try to use FQCN inside the annotations. Instead of
/**
* #ORM\OneToMany(targetEntity="Media", mappedBy="Object")
*/
try
/**
* #ORM\OneToMany(targetEntity="Application\Entity\Cms\Media", mappedBy="Object")
*/
in both entities.
Also i would like to recommend using camelCased entity properties instead of underscored_ones. Hydration process of the entities with underscored properties using DoctrineObject hydrator is problematic. You can find more details here.
BEWARE - Using unnecessary bi-directional associations increases your object graph and domain model complexity. Best practice is avoiding bi-directional associations if possible.
For this case, you can rewrite the same mapping using uni-directional relation between Post (Object) and Media entities if you don't need reverse access from Media to Post like
$media->getPost()
For example Application/Entity/Cms/Post.php :
/** #ORM\Entity **/
class Post
{
/**
* One to many, unidirectional
* #ORM\ManyToMany(targetEntity="Application\Entity\Cms\Media")
* #ORM\JoinTable(name="post_to_media",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={
* #ORM\JoinColumn(name="media_id", referencedColumnName="id",unique=true)
* })
**/
private $media;
public function __construct()
{
$this->media = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
and Application/Entity/Cms/Media.php :
/** #ORM\Entity **/
class Media
{
// No need to know about post
}

Doctrine 2.0 vs 2.1 cascade remove OneToMany

Hello I have problem when trying to cascade remove entities in OneToMany relations.
After a few hours of debugging I tried to downgrade the doctrine from the latest 2.1.2 to 2.0.2 and It suddenly starts working.
Imagin two entities Company and Address in relation 1:N.
/**
* #Entity
*/
class Company extends Entity
{
/**
* #var integer
* #id #Column(type="integer")
* #generatedValue
*/
private $id;
/**
* #var Collection
* #OneToMany(targetEntity="Address",mappedBy="company", cascade={"persist","remove"})
*/
private $addresses;
}
/**
* #Entity
*/
class Address extends Entity
{
/**
* #var integer
* #id #Column(type="integer")
* #generatedValue
*/
private $id;
/**
* #var Company
* #ManyToOne(targetEntity="Company", inversedBy="addresses")
* #JoinColumn(name="company_id", referencedColumnName="id",nullable=false)
*/
private $company;
}
when I try to remove the entity Company, I would like the assigned addresses will be removed as well.
$em->remove($company);
$em->flush();
In doctrine 2.1.2 the deletion of addresses is not performed so the integrity constraint fails. In version 2.0.2 there it works perfectly. Wierd thing on it is, if I use EntityAudit extension https://github.com/simplethings/EntityAudit the LogRevisionListener is corretly versioning the addresses entities (set them revtype = DEL) in doctrine 2.1.2 (of course in 2.0.2 as well) but the UnitOfWork is not removing it.
Is there any difference how to handle cascade removing in 2.0.2 and in 2.1.2?
Thank you very much
Try using this on the addresses attribute of your Company Class
#OneToMany(targetEntity="Address",mappedBy="company",
cascade={"persist"}, orphanRemoval=true)
I had the same problem... Relations were added or updated, but not deleted, even if I had cascade: [persist, remove].
I found out that I didn't need the "remove" attribute in "cascade", but I had to add the orphanRemoval: true.
I was going crazy, you made my day!
I have met the same problem and i have solved him with that code :
$em->remove($object);
$em->flush();
$em->remove($user);
$em->flush();
Maybe you can use a findAll on your company for the addresses and remove this with a foreach like that :
// Return all the addresses of the company
$addresses = $em->getRepository(...)->findAllAddressesByCompany($company);
$em->remove($company);
foreach ($address in $addresses)
{
$em->remove($address);
}
That's not a very good method but for now, that's all I've found.

Doctrine Cascade Options for OneToMany

I'm having a hard time making sense of the Doctrine manual's explanation of cascade operations and need someone to help me understand the options in terms of a simple ManyToOne relationship.
In my application, I have a table/entity named Article that has a foreign key field referencing the 'id' field in a table/entity named Topic.
When I create a new Article, I select the Topic from a dropdown menu. This inserts an integer into the 'topic_id' foreign key field in the Article table.
I have the $topic association set up in the Article entity like this:
/**
* #ManyToOne(targetEntity="Topic")
* #JoinColumn(name="topic_id", referencedColumnName="id", nullable=false)
*/
private $topic;
The Topic entity doesn't have any reciprocating annotation regarding the Article entity. Topics don't care what Articles reference them and nothing needs to happen to a Topic when an Article that references the Topic is deleted.
Because I'm not specifying the cascade operation in the Article entity, Doctrine throws an error when I try to create a new Article: "A new entity was found through a relationship that was not configured to cascade persist operations. Explicitly persist the new entity or configure cascading persist operations on the relationship."
So I know I need to choose a cascade operation to include in the Article entity, but how do I know which operation to choose in this situation?
From reading the Doctrine manual, "detach" sounds like the right option. But researching others' similar questions here and here makes me think I want to use "persist" instead.
Can anyone help me understand what "persist," "remove," "merge," and "detach" mean in terms of a simple ManyToOne relationship like the one I've described?
In the Doctrine2 documentation "9.6. Transitive persistence / Cascade Operations" there are few examples of how you should configure your entities so that when you persist $article, the $topic would be also persisted. In your case I'd suggest this annotation for Topic entity:
/**
* #OneToMany(targetEntity="Article", mappedBy="topic", cascade={"persist", "remove"})
*/
private $articles;
The drawback of this solution is that you have to include $articles collection to Topic entity, but you can leave it private without getter/setter.
And as #kurt-krueckeberg mentioned, you must pass the real Topic entity when creating new Article, i.e.:
$topic = $em->getRepository('Entity\Topic')->find($id);
$article = new Article($topic);
$em->persist($article);
$em->flush();
// perhaps, in this case you don't even need to configure cascade operations
Good luck!
If you have a #OneToMany unidirectional association, like that described in section 6.10 of the Doctrine Reference, then most likely you forgot to persist the Topic before calling flush. Don't set the topic_id primary key in Article. Instead set the Topic instance.
For example, given Article and Topic entities like these:
<?php
namespace Entities;
/**
#Entity
#Table(name="articles")
*/
class Article {
/**
* #Id
* #Column(type="integer", name="article_id")
* #GeneratedValue
*/
protected $id;
/**
* #Column(type="text")
*/
protected $text;
/**
* #ManyToOne(targetEntity="Topic", inversedBy="articles")
* #JoinColumn(name="topic_id", referencedColumnName="topic_id")
*/
protected $topic;
public function __construct($text=null)
{
if (!is_null($text)) {
$this->text = $text;
}
}
public function setArticle($text)
{
$this->text = $text;
}
public function setTopic(Topic $t)
{
$this->topic = $t;
}
}
<?php
namespace Entities;
/**
#Entity
#Table(name="topics")
*/
class Topic {
/**
* #Id
* #Column(type="integer", name="topic_id")
* #GeneratedValue
*/
protected $id;
public function __construct() {}
public function getId() {return $this->id;}
}
After you generate the schema:
# doctrine orm:schema-tool:create
your code to persist these entities would look like something this
//configuration omitted..
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
$topic = new Entities\Topic();
$article1 = new Entities\Article("article 1");
$article2 = new Entities\Article("article 2");
$article1->setTopic($topic);
$article2->setTopic($topic);
$em->persist($article1);
$em->persist($article2);
$em->persist($topic);
try {
$em->flush();
} catch(Exception $e) {
$msg= $e->getMessage();
echo $msg . "<br />\n";
}
return;
I hope this helps.

How to get a Respository from an Entity?

I have an Entity called Game with a related Repository called GameRepository:
/**
* #ORM\Entity(repositoryClass="...\GameRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Game {
/**
* #ORM\prePersist
*/
public function setSlugValue() {
$this->slug = $repo->createUniqueSlugForGame();
}
}
In the prePersist method, I need to ensure that the Game's slug field is unique, which requires a database query. To do the query, I need access to the EntityManager. I can get the EntityManager from inside GameRepository. So: how do I get the GameRespository from a Game?
You actually can get the repository in your entity and only during a lifecycle callback. You are very close to it, all you have to do is to receive the LifecycleEventArgs parameter.
Also see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html
use Doctrine\ORM\Event\LifecycleEventArgs;
/**
* #ORM\Entity(repositoryClass="...\GameRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Game {
/**
* #ORM\prePersist
*/
public function setSlugValue( LifecycleEventArgs $event ) {
$entityManager = $event->getEntityManager();
$repository = $entityManager->getRepository( get_class($this) );
$this->slug = $repository->createUniqueSlugForGame();
}
}
PS. I know this is an old question, but I answered it to help any future googlers.
You don't. Entities in Doctrine 2 are supposed to not know of the entity manager or the repository.
A typical solution to the case you present would be to add a method to the repository (or a service class) which is used to create (or called to store) new instances, and also produces a unique slug value.
you can inject the doctrine entity manager in your entity
(using JMSDiExtraBundle)
and have the repository like this:
/**
* #InjectParams({
* "em" = #Inject("doctrine.orm.entity_manager")
* })
*/
public function setInitialStatus(\Doctrine\ORM\EntityManager $em) {
$obj = $em->getRepository('AcmeSampleBundle:User')->functionInRepository();
//...
}
see this : http://jmsyst.com/bundles/JMSDiExtraBundle/1.1/annotations
In order to keep the logic encapsulated without having to change the way you save the entity, instead of the simple prePersist lifecycle event you will need to look at using the more powerful Doctrine events which can get access to more than just the entity itself.
You should probably look at the DoctrineSluggableBundle or StofDoctrineExtensionsBundle bundles which might do just what you need.