NotionCommotion Posted February 1, 2021 Share Posted February 1, 2021 I am doing something stupid, and just can't see it and hoping someone has better eyes than mine. I have two Doctrine entities which extend another class using single class inheritance, but when retrieving the collection of either of the two child classes, I get the collection of the parent (i.e. combination of both child classes). I've experimented with making the parent abstract and not including it in the discriminator map but no change. In hopes to identifying my mistake, I created a new Symfony project with just the relevant classes and show all steps below. Initial install by running the following: symfony new test composer update composer require doctrine/orm composer require migrations composer require maker --dev composer require security Using "php bin/console make:user", I created three users: AbstractUser, OwnerUser, VendorUser. I then edited all three entities and the owner and vendor repositories as follows: <?php namespace App\Entity; use App\Repository\AbstractUserRepository; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\UserInterface; /** * @ORM\Entity(repositoryClass=AbstractUserRepository::class) * @ORM\InheritanceType("SINGLE_TABLE") * @ORM\DiscriminatorColumn(name="discr", type="string") * @ORM\DiscriminatorMap({"owner" = "OwnerUser", "vendor" = "VendorUser", "abstract" = "AbstractUser"}) */ class AbstractUser implements UserInterface { // No changes made } <?php namespace App\Entity; use App\Repository\OwnerUserRepository; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(repositoryClass=OwnerUserRepository::class) */ class OwnerUser extends AbstractUser { } <?php namespace App\Entity; use App\Repository\VendorUserRepository; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(repositoryClass=VendorUserRepository::class) */ class VendorUser extends AbstractUser { } No change to AbstractUserRepository. <?php namespace App\Repository; class OwnerUserRepository extends AbstractUserRepository { } <?php namespace App\Repository; class VendorUserRepository extends AbstractUserRepository { } Then I migrated the DB. php bin/console doctrine:schema:drop --full-database --force rm migrations/* php bin/console make:migration php bin/console doctrine:migrations:migrate Next I created two OwnerUsers and one VendorUser using a command script I wrote shown at the end of this post. # php bin/console app:tester create OwnerUser roles=[] # php bin/console app:tester create OwnerUser roles=[] # php bin/console app:tester create VendorUser roles=[] Checked the PostSQL database. facdocs=> \d List of relations Schema | Name | Type | Owner --------+-----------------------------+----------+--------- public | abstract_user | table | facdocs public | abstract_user_id_seq | sequence | facdocs public | doctrine_migration_versions | table | facdocs (3 rows) facdocs=> select * from abstract_user; id | email | roles | password | discr ----+-------------+-------+----------------+-------- 1 | setEmail158 | [] | setPassword146 | owner 2 | setEmail87 | [] | setPassword101 | owner 3 | setEmail62 | [] | setPassword20 | vendor (3 rows) facdocs=> Next, I retrieved the entities using Doctrine along with the beforementioned test script. [michael@devserver test]$ php bin/console app:tester read VendorUser Test Entity Manager ============ command: read entity: VendorUser class: \App\Entity\VendorUser properties: [] array(3) { [0]=> object(stdClass)#293 (5) { ["__CLASS__"]=> string(20) "App\Entity\OwnerUser" ["id:App\Entity\AbstractUser:private"]=> int(1) ["email:App\Entity\AbstractUser:private"]=> string(11) "setEmail158" ["roles:App\Entity\AbstractUser:private"]=> string(8) "Array(0)" ["password:App\Entity\AbstractUser:private"]=> string(14) "setPassword146" } [1]=> object(stdClass)#294 (5) { ["__CLASS__"]=> string(20) "App\Entity\OwnerUser" ["id:App\Entity\AbstractUser:private"]=> int(2) ["email:App\Entity\AbstractUser:private"]=> string(10) "setEmail87" ["roles:App\Entity\AbstractUser:private"]=> string(8) "Array(0)" ["password:App\Entity\AbstractUser:private"]=> string(14) "setPassword101" } [2]=> object(stdClass)#358 (5) { ["__CLASS__"]=> string(21) "App\Entity\VendorUser" ["id:App\Entity\AbstractUser:private"]=> int(3) ["email:App\Entity\AbstractUser:private"]=> string(10) "setEmail62" ["roles:App\Entity\AbstractUser:private"]=> string(8) "Array(0)" ["password:App\Entity\AbstractUser:private"]=> string(13) "setPassword20" } } [michael@devserver test]$ [michael@devserver test]$ php bin/console app:tester read OwnerUser Test Entity Manager ============ command: read entity: OwnerUser class: \App\Entity\OwnerUser properties: [] array(3) { [0]=> object(stdClass)#293 (5) { ["__CLASS__"]=> string(20) "App\Entity\OwnerUser" ["id:App\Entity\AbstractUser:private"]=> int(1) ["email:App\Entity\AbstractUser:private"]=> string(11) "setEmail158" ["roles:App\Entity\AbstractUser:private"]=> string(8) "Array(0)" ["password:App\Entity\AbstractUser:private"]=> string(14) "setPassword146" } [1]=> object(stdClass)#294 (5) { ["__CLASS__"]=> string(20) "App\Entity\OwnerUser" ["id:App\Entity\AbstractUser:private"]=> int(2) ["email:App\Entity\AbstractUser:private"]=> string(10) "setEmail87" ["roles:App\Entity\AbstractUser:private"]=> string(8) "Array(0)" ["password:App\Entity\AbstractUser:private"]=> string(14) "setPassword101" } [2]=> object(stdClass)#358 (5) { ["__CLASS__"]=> string(21) "App\Entity\VendorUser" ["id:App\Entity\AbstractUser:private"]=> int(3) ["email:App\Entity\AbstractUser:private"]=> string(10) "setEmail62" ["roles:App\Entity\AbstractUser:private"]=> string(8) "Array(0)" ["password:App\Entity\AbstractUser:private"]=> string(13) "setPassword20" } } [michael@devserver test]$ As seen, when requesting a collection of either OwnerUsers or VendorUsers, I am retrieving the combination of both collections. I am sure it is totally user (i.e. me) error, but just don't see it. Are you able to see my stupid mistake? Thanks My test script is below: <?php namespace App\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Common\Util\Debug; class Tester extends Command { // the name of the command (the part after "bin/console") protected static $defaultName = 'app:tester'; private $entityManager; public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; parent::__construct(); } protected function configure() { $this ->setDescription('Doctrine Object Tester. --help') ->setHelp('This command allows you to query a single entity...') ->addArgument('cmd', InputArgument::REQUIRED, 'The command name [create, read].') ->addArgument('entity', InputArgument::REQUIRED, 'The name of the entity.') ->addArgument('properties', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'properies (foo=foo bla=[]') ; } private function getProperties($input) { $properties=[]; foreach($input as $property) { $property=explode('=', $property); $name = trim($property[0]); $value = trim($property[1]); if($value === '[]') { $value = []; } elseif($value === 'null') { $value = null; } elseif(substr($value, 0, 1) === '/') { $value = explode('/', substr($value, 1)); $class = ucfirst(substr($value[0], 0, -1)); $class = '\App\Entity\\'.$class; if(count($value)===1) { $value = $this->entityManager->getRepository($class)->findAll(); } else { $value = $this->entityManager->getRepository($class)->find($value[1]); } } $properties[$name] = $value; } return $properties; } protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln([ 'Test Entity Manager', '============', '', ]); // retrieve the argument value using getArgument() $cmd = $input->getArgument('cmd'); $output->writeln('command: '.$cmd); $entity = $input->getArgument('entity'); $output->writeln('entity: '.$entity); $class = '\App\Entity\\'.$entity; $output->writeln('class: '.$class); $properties = $this->getProperties($input->getArgument('properties')); $output->writeln('properties: '.json_encode($properties)); switch($cmd) { case 'create': $entity = new $class; foreach(get_class_methods($entity) as $method) { if(substr($method, 0, 3)==='set') { $m = lcfirst(substr($method, 3)); $entity->$method(array_key_exists($m, $properties)?$properties[$m]:$method.rand(0,200)); } } $this->entityManager->persist($entity); $this->entityManager->flush(); $output->writeln('entity created'); break; case 'read': Debug::dump($this->entityManager->getRepository($class)->findAll()); break; default: $output->writeln('INVALID COMMAND: '.$cmd.'. Only create and read are supported.'); } return Command::SUCCESS; } } Quote Link to comment https://forums.phpfreaks.com/topic/312083-stupid-mistake-with-doctrine-entities-and-inheritance/ Share on other sites More sharing options...
NotionCommotion Posted February 1, 2021 Author Share Posted February 1, 2021 I have no idea why this would make a different, but if I remove the repository declaration in the child entity, I now get the desired results. Of course, now I can't have separate repositories for each sub-class... Does not work: /** * @ORM\Entity * @ORM\Entity(repositoryClass=OwnerUserRepository::class) */ class OwnerUser extends AbstractUser { } Works: /** * @ORM\Entity */ class OwnerUser extends AbstractUser { } Quote Link to comment https://forums.phpfreaks.com/topic/312083-stupid-mistake-with-doctrine-entities-and-inheritance/#findComment-1584191 Share on other sites More sharing options...
kicken Posted February 2, 2021 Share Posted February 2, 2021 Just a guess but what if you don't have your repositories extend AbstractUserRepository and instead just be their own separate repositories? 1 Quote Link to comment https://forums.phpfreaks.com/topic/312083-stupid-mistake-with-doctrine-entities-and-inheritance/#findComment-1584199 Share on other sites More sharing options...
NotionCommotion Posted February 6, 2021 Author Share Posted February 6, 2021 On 2/2/2021 at 6:48 AM, kicken said: Just a guess but what if you don't have your repositories extend AbstractUserRepository and instead just be their own separate repositories? Thanks kicken. I half hearted tested your suggestion with no expectations, and behold you are absolutely correct! Then, I was more confused regarding why it worked, but then spotted my stupid mistake. Previously I've always manually created my Doctrine repositories and added a 'create()` method to each to create an entity. Symfony does almost the same but injects the class name of the to be created class in the constructor. Happy to find that it was nothing magical but just normal PHP. My final solution still extends AbstractUserRepository, but each entity has its own constructor. Thanks again! <?php namespace App\Repository; use App\Entity\Vendor; use Doctrine\Persistence\ManagerRegistry; class VendorRepository extends AbstractUserRepository { public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Vendor::class); } } Quote Link to comment https://forums.phpfreaks.com/topic/312083-stupid-mistake-with-doctrine-entities-and-inheritance/#findComment-1584256 Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.