mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 06:52:09 +01:00
Persisting entities with cross-schema relationships between multiple entity managers #5472
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @brandinchiu on GitHub (Mar 20, 2017).
Originally assigned to: @Ocramius on GitHub.
I am looking for any up-to-date documentation on Doctrine ORM's ability to handle cross-schema relationships between entities. This currently doesn't appear to be possible, as the entity manager of the persisting entity does not understand the state of entity being managed by the other manager.
This leads to confusing states where an ORMInvalidArgumentException is thrown (due to an unfulfilled cascade), or a MySQL unique constraint fails (Doctrine tries to persist the related entity, but it's clearly already in the DB).
Is there a specific way to handle this via Doctrine config, or is this still an unsupported feature?
Will it ever be a supported feature, or is there an official stance Doctrine has on this use-case?
Thanks!
@Ocramius commented on GitHub (Mar 21, 2017):
Sharing entities across multiple EntityManager instances is not supported,
and will need a separate managed entity copy instead.
On 20 Mar 2017 19:11, "Brandin Chiu" notifications@github.com wrote:
@brandinchiu commented on GitHub (Mar 21, 2017):
@Ocramius: could you point me in the direction of how I would accomplish this?
@Ocramius commented on GitHub (Mar 22, 2017):
If you never did this before, you can edit the files directly from the
github UI and send them back for inclusion in our repository.
On 21 Mar 2017 2:06 p.m., "Brandin Chiu" notifications@github.com wrote:
@brandinchiu commented on GitHub (Mar 22, 2017):
My mistake, I thought this was existent functionality that I just wasn't utilising. My familiarity with the guts of Doctrine is pretty minimal, and I'm not sure how effective a PR from me would be -- but I'll take a gander and see what I can come up with.
@Ocramius commented on GitHub (Mar 22, 2017):
@brandinchiu you just need to replace that
::classwith a string reference (FQN)@brandinchiu commented on GitHub (Mar 22, 2017):
I'm afraid I'm not clear on which ::class reference you are referring to.
@Ocramius commented on GitHub (Mar 22, 2017):
@brandinchiu urgh, sorry, totally mixed up. My replies should have gone to a different thread. Sorry! :-(
Backtracking a bit:
You would need to communicate across the separate entity managers via identifiers only:
Would translate to something like:
@brandinchiu commented on GitHub (Mar 22, 2017):
In this example, are these services entity managers, or my own custom logic running on top of them? I already have something like this that is managing the entities on my end, the problems I'm running into are at the last leg, when I need the entities persisted.
Fetching objects has been no problem, including the ones with the cross-schema relations.
I've played around with some changes to the underlying Doctrine code, and I've managed to get as far (in testing) as to prevent the EM1 from trying to cascade-persist the foreign entity from the other schema.
The problem I run into after that is that Entity1, managed by EM1, doesn't maintain the ID from Entity2(foreign)/EM2, even though EM1 knows about the entity. When it gets to the point where it needs to actually write Entity1, the foreign key is null. I haven't found an intelligent way to reconcile that yet, mostly because I'm still trying to figure out how Doctrine handles the in-memory storage of foreign entities.
@Ocramius commented on GitHub (Mar 22, 2017):
I would need a piece of code that reproduces your issue in isolation to get you to a workaround. Can't help just by imagining what you are doing, sorry...
@brandinchiu commented on GitHub (Mar 22, 2017):
Of course; here's a more concrete example of what I'm attempting to do:
Assume I have 3 schemas:
data_1/2 are application databases storing relevant information for two clients. It stores user, financial, and other information for those clients.
data_index is an array of enum-like tables, which consist of ID->Name pairs. These are more or less non-transactional, static values. Things like roles, types, languages and countries.
My individual database servers will consist of multiple data_n schemas, and ideally, one data_index schema, shared across them.
The example I am currently working with is persisting new users to a data_n schema, which requires a valid role ID from data_index.role.
What I've attempted to do for testing is to introduce a new cascade option: ignore. I've modified the list of acceptable cascade parameters, and modified the foreach loop in UnitOfWork:735, to not compute association changes if $assoc['isCascadeIgnore'].
This obviously prevents entity manager managing *data_n.user * from trying to persist the role entity from the foreign schema. However, once the entity manager tries to actually persist the user, it confusingly doesn't know the role ID, even though Entity\User->getRole()->getId() is not null.
Would you like the actual compilation of my changes, or is this enough to suggest a course of action?
@Ocramius commented on GitHub (Mar 22, 2017):
I'm asking for an actual code example.
@brandinchiu commented on GitHub (Mar 22, 2017):
Entity Metadata
\Doctrine\ORM\Mapping\MappingException:776 (added new cascade option to exception message)
\Doctrine\ORM\Mapping\ClassMetadataInfo:1517 (added new cascade option to array_intersect and added new mapping index 'isCascadeIgnore')
\Doctrine\ORM\UnitOfWork:735 (prevent $this->computeAssociationChanges from running if isCascadeIgnore)
Is this better? Or do you want an actual PR?
@Ocramius commented on GitHub (Mar 22, 2017):
No, I'm asking where you are sharing an entity reference... :-\
@brandinchiu commented on GitHub (Mar 22, 2017):
Like, where I'm instantiating the user entity and then setting the role?
@Ocramius commented on GitHub (Mar 22, 2017):
Yes: without going into ORM internals, how are you using the ORM API? Can you make an example that, using multiple entity managers, reproduces your issue?
@brandinchiu commented on GitHub (Mar 22, 2017):
I'm working with Symfony, and have heavily abstracted out how I work with ORM. I work with controller classes that wrap around entities to provide functionality.
UserController::save() basically just runs $em->persist and $em->flush on the wrapped entity.
Core\Index\RoleController::getRole(Enum\Role::ROLE_USER) just returns an instance of RoleController with a preloaded $entity property based on the passed enum.
The UserController->setRole() method takes a RoleController and updates the underlying user entity with that of the RoleController:
UserController::__construct()
UserController::getClass() returns the expected name of the user entity, in this case, User.
UserController::getControllerNamespace() returns the expected name of the entity manager, either 'index', or 'account'.
UserController::nullEntity() returns an instance of the entity above, with null properties.
UserController::set sets the underlying entity properties.
The result of the topmost section is a UserController, with a ..\Entity\User marked as new by UserController::getEntityManager(), with all the property values that were set above. If I dump UserController->getEntity()->getRole()->getId, I get the same value as Enum\Role::ROLE_USER.
This is the doctrine config:
I dunno how helpful this is to you ... unless you want me to dump my whole project somewhere for you to swim through ...
@Ocramius commented on GitHub (Mar 22, 2017):
No, that's not going to be useful, but generally:
Will lead to a mess (and just fail).
Yes, but where is the entity passed across multiple entity managers? As far as I can see, your current controllers all work with one entity and one entity manager...
Is this the interaction?
@brandinchiu commented on GitHub (Mar 22, 2017):
What's essentially happening is:
@Ocramius commented on GitHub (Mar 22, 2017):
Right, that's never going to happen. You will need to get a fresh copy of
$rolefrom$em1.@brandinchiu commented on GitHub (Mar 22, 2017):
Is this even possible? How can $em1 know about a Role when it points to a different database?
@Ocramius commented on GitHub (Mar 22, 2017):
Then you need to break the association, and use a scalar field for that association:
@brandinchiu commented on GitHub (Mar 22, 2017):
So there's no reasonable way Doctrine would ever support this kind of behaviour?
Moving to a scalar, non-entity(object) relationship was the next thing on my list to try.
@Ocramius commented on GitHub (Mar 22, 2017):
No, you'd need a single
EntityManagerthat connects to multiple DBs instead.@brandinchiu commented on GitHub (Mar 22, 2017):
And that's currently not possible, correct?
@Ocramius commented on GitHub (Mar 22, 2017):
@brandinchiu depends on RDBMS. For instance, in Postgres/MySQL, you can span across different schema with a single DB connection.
Otherwise, an alternate solution is to write a
Doctrine\DBAL\Connectionclass that is able to select which DB to send a query to, depending on the SQL statement. That is NOT a simple task (estimating weeks of work).@brandinchiu commented on GitHub (Mar 22, 2017):
I'm using MySQL. Can you point me in the direction of the documentation outlining how to configure a single entity manager to interact with two different databases/schema?
EDIT: or explain that here, if it's easier/faster.
@Ocramius commented on GitHub (Mar 22, 2017):
@brandinchiu you can just use a single DB connection, and map the entities with
@Table(name="foo", schema="bar")@brandinchiu commented on GitHub (Mar 22, 2017):
How would I accomplish this with yaml configuration?
@Ocramius commented on GitHub (Mar 22, 2017):
@brandinchiu no clue: see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/yaml-mapping.html
@Ocramius commented on GitHub (Mar 22, 2017):
Closing here, as the issue is resolved.
@brandinchiu commented on GitHub (Mar 22, 2017):
I appreciate the assistance.