3v4l.org

run code in 300+ PHP versions simultaneously
<?php # Part 1: Prepare the classes. There should me lots of more code to make it robust, but I'm trying the PoC simple. class Repository { public array $data; /** * Works out the namespaced key and stores the value twice. * * If we're storing objects, they're stored as a reference anyway, so it doesn't matter that the same thing is there twice. * * If we store a scalar, then yes, we take up 2x the memory, but I think it's not a common use case and it's not a big deal. */ public function setReference(string $key, mixed $identity): void { // Stores the last $identity set with $key. If you don't re-use keys, this always keeps your object. Previous behaviour. $this->data[$key] = $identity; // Stores the namespaced identity. If you re-use keys and use the type as discriminator, this is what you'll be getting things back from. if (is_object($identity)) { $nsKey = $key.$this->getRealClass($identity::class); } else { $nsKey = $key.(get_debug_type($identity) ?? ''); } $this->data[$nsKey] = $identity; } /** * The @template notation below tells static analysers what value is returned when $type is provided. I tested this with PHPStorm - $boss = $repository->getReference('foo', Boss::class) correctly identifies $boss as Boss::class object. * * @template T * @param string $key * @param class-string<T> $type Could be a class name, or even a 'string' or 'int' value. * @return T */ public function getReference(string $key, string $type = null) { // If the $key doesn't exist, it doesn't matter what $type we might be asking for: the $key was never used when setting a value if (!array_key_exists($key, $this->data)) { throw new \Exception('Fixture not found'); } // No $type provided or not found using the namespaced key if (!$type) { return $this->data[$key]; } // The correct $key and $type combination exist - all good! if (array_key_exuists($key.$type, $this->data)) { // I'd assert the type here too, but for sake of example simplicity of this PoC, let's just not return $this->data[$key.$type]; } // If we made it here, the $key was used to set a value, but the $key we are asking for is not what the object was determined to be when it was being set. // The not-namespaced key happens to be of the correct type - this covers storing the Employee::class and asking for Person::class if (class_exists($type) && $this->data[$key] instanceof $type) { return $this->data[$key]; } // This means the last object stored using $key was not of $type. Since we're clearly saying we want a $type, we can't return anything throw new \Exception(sprintf( 'Fixture %s was found, but is type %s, not %s. Please ask for the correct type.', $key, get_debug_type($this->data[$key]), $type, )); } protected function getRealClass($className) { if (substr($className, -5) === 'Proxy') { return substr($className, 0, -5); } return $className; } } abstract class Person { public function __construct(public string $name) {} } class Boss extends Person {} class Employee extends Person {} # Part 2: Basic use case PoC $repository = new Repository(); $bossJohn = new Boss('John'); $bossJane = new Boss('Jane'); $employeeJim = new Employee('Jim'); $repository->setReference('boss', $bossJohn); $repository->setReference('boss', $bossJane); // overwrites 'boss' key with Jane because John and Jane are the same class assert($repository->getReference('boss', Boss::class) === $bossJane); // Retrieves last set boss that overwrites previous bosses assert($repository->getReference('boss') === $bossJane); // retrieves the non-namespaced value $repository->setReference('person', $bossJohn); $repository->setReference('person', $employeeJim); // adds Jim under duplicate 'person' key because of different classes assert($repository->getReference('person', Boss::class) === $bossJohn); // retrieves the correct person assert($repository->getReference('person', Employee::class) === $employeeJim); // retrieves the correct person assert($repository->getReference('person') === $employeeJim); # Part 3: PoC of things that I don't think are possible if https://github.com/doctrine/data-fixtures/pull/409 gets merged as is on 8th Feb 2023: // This is where things get intereesting, you can store other things than objects, if you want. Think of testing encryption keys generated on the fly etc. $repository->setReference('number', 31337); assert($repository->getReference('number') === 31337); $repository->setReference('number', 31337.0); assert($repository->getReference('number', 'float') === 31337.0); // Using this method we can use any parent in the inheritance structure assert($repository->getReference('person', Person::class) === $employeeJim); // Returns the last set 'person' even thought we're using a parent Person::class, not Employee::class // Using this method we can use any parent in the inheritance structure assert($repository->getReference('person', Boss::class) === $bossJohn); // Returns the correct fixture that duplicates keys, as long as we know what is the exact class we're after echo 'Success.';
Output for 8.1.20 - 8.1.29, 8.2.2 - 8.2.23, 8.3.0 - 8.3.11
Fatal error: Uncaught Error: Call to undefined function array_key_exuists() in /in/HuZD6:52 Stack trace: #0 /in/HuZD6(105): Repository->getReference('boss', 'Boss') #1 {main} thrown in /in/HuZD6 on line 52
Process exited with code 255.

preferences:
61.86 ms | 406 KiB | 5 Q