Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Undefined property error when a property exists in proxied class but not initialised in proxy #511

Closed
Frederick888 opened this issue Oct 7, 2019 · 16 comments
Assignees

Comments

@Frederick888
Copy link

I originally reported this issue at doctrine/mongodb-odm#2080 and was asked to file another ticket here.

I encountered this problem after upgrading to mongodb-odm v2.0.0 and since some properties in my model are never set in the database, it results in Undefined property errors when the generated proxy tries to get their values:

    public function __get($name)
    {
        $this->initializerc3072 && ! $this->initializationTrackerce44e && $this->callInitializer728ea('__get', array('name' => $name));

        if (isset(self::$publicPropertiesda3cc[$name])) {
            return $this->$name;  // BREAKS HERE
        }
        // omitted
    }

I just went through the slides linked from readme but I honestly have got only a rough gist of the idea. Please let me know if I should provide any other details.

@Ocramius
Copy link
Owner

Ocramius commented Oct 7, 2019

This seems like the correct behavior to mr, but let's inspect further.

Can you maybe show your entity, and maybe the generated proxy file?

@Frederick888
Copy link
Author

Err... It now breaks due to the protected $excludedFromArray I've got in my BaseModel at

            // check protected property access via compatible class
            $callers      = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
            $caller       = isset($callers[1]) ? $callers[1] : [];
            $object       = isset($caller['object']) ? $caller['object'] : '';
            $expectedType = self::$protectedProperties1a2d1[$name];

            if ($object instanceof $expectedType) {
                return $this->$name; // HERE
            }

Was I being crazy...?!

Anyway... Here are all the files I've got which are possibly related (btw I'm using Laravel):

<?php

namespace App\Doctrine;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/**
 * @ODM\Document(collection="labs")
 * @ODM\HasLifecycleCallbacks
 */
class Lab extends BaseModel
{
    use Traits\CreatedAt, Traits\UpdatedAt;
    /**
     * @ODM\Id
     * @var string
     */
    public $_id;
    /**
     * @ODM\EmbedOne(targetDocument=Address::class)
     * @var Address
     */
    public $address;
}
<?php

namespace App\Doctrine;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/** @ODM\EmbeddedDocument */
class Address extends BaseModel
{
    /**
     * @ODM\Field(type="string")
     * @var string
     */
    public $street;
    /**
     * @ODM\Field(type="string")
     * @var string
     */
    public $city;
    /**
     * @ODM\Field(type="string")
     * @var string
     */
    public $state;
    /**
     * @ODM\Field(type="string")
     * @var string
     */
    public $country;
    /**
     * @ODM\Field(type="string")
     * @var string
     */
    public $zip;
}
<?php

namespace App\Doctrine;

use Carbon\Carbon;
use MyCLabs\Enum\Enum;

class BaseModel
{
    protected $excludedFromArray = ['_id', 'lazyPropertiesDefaults'];
    private $preserveOriginalName = false;

    public function __construct(array $data = null)
    {
        if (!empty($data)) {
            foreach ($data as $key => $value) {
                $this->$key = $value;
            }
        }
    }

    public function __get($name)
    {
        $accessor = 'get_' . $this->snakeCase($name);
        if (method_exists($this, $accessor)) {
            return $this->$accessor();
        }
        return $this->$name ?? null;
    }

    public function __set($name, $value)
    {
        $mutator = 'set_' . $this->snakeCase($name);
        if (method_exists($this, $mutator)) {
            // an additional bool parameter ($toRemove = false) is provided
            $this->$mutator($value, false);
        } else {
            $this->$name = $value;
        }
    }

    public function __unset($name)
    {
        $this->__set($name, null);
    }

    public function __isset($name)
    {
        return $this->__get($name) !== null;
    }

    public function __clone()
    {
        foreach (get_object_vars($this) as $key => $value) {
            $this->$key = $this->cloneRecursively($value);
        }
    }

    /**
     * @return array
     */
    public function getExcludedFromArray(): array
    {
        return $this->excludedFromArray;
    }

    /**
     * @param array $excludedFromArray
     */
    public function setExcludedFromArray(array $excludedFromArray): void
    {
        $this->excludedFromArray = $excludedFromArray;
    }

    /**
     * @param mixed $item
     * @return mixed
     */
    private function cloneRecursively($item)
    {
        if (is_object($item)) {
            return clone $item;
        }
        if (is_array($item)) {
            return array_map([self::class, 'cloneRecursively'], $item);
        }
        return $item;
    }

    public function toArray(): array
    {
        $reflect = new \ReflectionClass($this);
        $result = [];
        foreach ($reflect->getProperties(\ReflectionProperty::IS_PUBLIC) as $key) {
            $key = $key->getName();
            if (strpos($key, '__') === 0
                || in_array($key, $this->getExcludedFromArray(), true)
                || method_exists($this, 'get_' . $this->snakeCase($key))) {
                continue;
            }
            $result[$key] = $this->toArrayRecursively($this->$key);
        }
        foreach ($reflect->getMethods() as $method) {
            $method = $method->getName();
            if (preg_match('/^get_(.+)$/', $method, $matches)) {
                $key = $matches[1];
                if (strpos($key, '__') === 0 || in_array($key, $this->getExcludedFromArray(), true)) {
                    continue;
                }
                $result[$key] = $this->toArrayRecursively($this->{$matches[0]}());
            }
        }
        return $result;
    }

    /**
     * @param mixed $item
     * @return mixed
     */
    private function toArrayRecursively($item)
    {
        if ($item instanceof Carbon) {
            return $item->format('c');
        }
        if ($item instanceof \DateTimeInterface) {
            return Carbon::instance($item)->format('c');
        }
        if ($item instanceof Enum) {
            return $item->getValue();
        }
        if ($item instanceof \IteratorAggregate) {
            $item = $item->getIterator();
        }
        if ($item instanceof \Iterator) {
            $item = iterator_to_array($item);
        }
        if (is_object($item) && method_exists($item, 'toArray')) {
            return $item->toArray();
        }
        if (is_array($item)) {
            return array_map([self::class, 'toArrayRecursively'], $item);
        }
        return $item;
    }

    protected function snakeCase($name): string
    {
        if ($this->preserveOriginalName) {
            return $name;
        }
        return strtolower(preg_replace('/[^A-Z0-9]+/i', '_', $name));
    }
}
<?php

namespace App\Doctrine\Traits;

use Carbon\Carbon;
use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs;

trait CreatedAt
{
    /**
     * @ODM\Field(type="date")
     * @var \DateTime|Carbon
     */
    public $created_at;

    /**
     * @ODM\PrePersist
     * @param LifecycleEventArgs $eventArgs
     */
    public function doPrePersistCreatedAt(LifecycleEventArgs $eventArgs): void
    {
        if (empty($this->created_at)) {
            $this->created_at = Carbon::now();
        }
    }
}
<?php

namespace App\Doctrine\Traits;

use Carbon\Carbon;
use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs;

trait UpdatedAt
{
    /**
     * @ODM\Field(type="date")
     * @var \DateTime|Carbon
     */
    public $updated_at;

    /**
     * @ODM\PrePersist
     * @param LifecycleEventArgs $eventArgs
     */
    public function doPrePersistUpdatedAt(LifecycleEventArgs $eventArgs): void
    {
        $this->updated_at = Carbon::now();
    }

    /**
     * @ODM\PreUpdate
     * @param LifecycleEventArgs $eventArgs
     */
    public function doPreUpdateUpdatedAt(LifecycleEventArgs $eventArgs): void
    {
        $this->updated_at = Carbon::now();
    }
}
<?php

namespace Proxies\__PM__\App\Doctrine\Lab;

class Generated6805517574cc3696bcee09ef22e16ded extends \App\Doctrine\Lab implements \ProxyManager\Proxy\GhostObjectInterface
{

    /**
     * @var \Closure|null initializer responsible for generating the wrapped object
     */
    private $initializerc3072 = null;

    /**
     * @var bool tracks initialization status - true while the object is initializing
     */
    private $initializationTrackerce44e = false;

    /**
     * @var bool[] map of public properties of the parent class
     */
    private static $publicPropertiesda3cc = [
        'address' => true,
        'created_at' => true,
        'updated_at' => true,
    ];

    /**
     * @var array[][] visibility and default value of defined properties, indexed by
     * property name and class name
     */
    private static $privatePropertiese171f = [
        'preserveOriginalName' => [
            'App\\Doctrine\\BaseModel' => true,
        ],
    ];

    /**
     * @var string[][] declaring class name of defined protected properties, indexed by
     * property name
     */
    private static $protectedProperties1a2d1 = [
        'excludedFromArray' => 'App\\Doctrine\\BaseModel',
    ];

    private static $signature6805517574cc3696bcee09ef22e16ded = 'YTo0OntzOjk6ImNsYXNzTmFtZSI7czoxNjoiQXBwXERvY3RyaW5lXExhYiI7czo3OiJmYWN0b3J5IjtzOjQ0OiJQcm94eU1hbmFnZXJcRmFjdG9yeVxMYXp5TG9hZGluZ0dob3N0RmFjdG9yeSI7czoxOToicHJveHlNYW5hZ2VyVmVyc2lvbiI7czo0NjoiMi4yLjNANGQxNTQ3NDJlMzFjMzUxMzdkNTM3NGM5OThlOGY4NmI1NGRiMmUyZiI7czoxMjoicHJveHlPcHRpb25zIjthOjE6e3M6MTc6InNraXBwZWRQcm9wZXJ0aWVzIjthOjE6e2k6MDtzOjM6Il9pZCI7fX19';

    /**
     * Triggers initialization logic for this ghost object
     *
     * @param string  $methodName
     * @param mixed[] $parameters
     *
     * @return mixed
     */
    private function callInitializer728ea($methodName, array $parameters)
    {
        if ($this->initializationTrackerce44e || ! $this->initializerc3072) {
            return;
        }

        $this->initializationTrackerce44e = true;

        $this->address = NULL;
        $this->created_at = NULL;
        $this->updated_at = NULL;
        $this->excludedFromArray = array (
          0 => '_id',
          1 => 'lazyPropertiesDefaults',
        );
        static $cacheApp_Doctrine_BaseModel;

        $cacheApp_Doctrine_BaseModel ?: $cacheApp_Doctrine_BaseModel = \Closure::bind(function ($instance) {
            $instance->preserveOriginalName = false;
        }, null, 'App\\Doctrine\\BaseModel');

        $cacheApp_Doctrine_BaseModel($this);




        $properties = [
            'address' => & $this->address,
            'created_at' => & $this->created_at,
            'updated_at' => & $this->updated_at,
            '' . "\0" . '*' . "\0" . 'excludedFromArray' => & $this->excludedFromArray,
        ];

        static $cacheFetchApp_Doctrine_BaseModel;

        $cacheFetchApp_Doctrine_BaseModel ?: $cacheFetchApp_Doctrine_BaseModel = \Closure::bind(function ($instance, array & $properties) {
            $properties['' . "\0" . 'App\\Doctrine\\BaseModel' . "\0" . 'preserveOriginalName'] = & $instance->preserveOriginalName;
        }, $this, 'App\\Doctrine\\BaseModel');

        $cacheFetchApp_Doctrine_BaseModel($this, $properties);

        $result = $this->initializerc3072->__invoke($this, $methodName, $parameters, $this->initializerc3072, $properties);
        $this->initializationTrackerce44e = false;

        return $result;
    }

    /**
     * Constructor for lazy initialization
     *
     * @param \Closure|null $initializer
     */
    public static function staticProxyConstructor($initializer)
    {
        static $reflection;

        $reflection = $reflection ?? $reflection = new \ReflectionClass(__CLASS__);
        $instance = $reflection->newInstanceWithoutConstructor();

        unset($instance->address, $instance->created_at, $instance->updated_at, $instance->excludedFromArray);

        \Closure::bind(function (\App\Doctrine\BaseModel $instance) {
            unset($instance->preserveOriginalName);
        }, $instance, 'App\\Doctrine\\BaseModel')->__invoke($instance);

        $instance->initializerc3072 = $initializer;

        return $instance;
    }

    public function __get($name)
    {
        $this->initializerc3072 && ! $this->initializationTrackerce44e && $this->callInitializer728ea('__get', array('name' => $name));

        if (isset(self::$publicPropertiesda3cc[$name])) {
            return $this->$name;
        }

        if (isset(self::$protectedProperties1a2d1[$name])) {
            if ($this->initializationTrackerce44e) {
                return $this->$name;
            }

            // check protected property access via compatible class
            $callers      = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
            $caller       = isset($callers[1]) ? $callers[1] : [];
            $object       = isset($caller['object']) ? $caller['object'] : '';
            $expectedType = self::$protectedProperties1a2d1[$name];

            if ($object instanceof $expectedType) {
                return $this->$name;
            }

            $class = isset($caller['class']) ? $caller['class'] : '';

            if ($class === $expectedType || is_subclass_of($class, $expectedType) || $class === 'ReflectionProperty') {
                return $this->$name;
            }
        } elseif (isset(self::$privatePropertiese171f[$name])) {
            // check private property access via same class
            $callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
            $caller  = isset($callers[1]) ? $callers[1] : [];
            $class   = isset($caller['class']) ? $caller['class'] : '';

            static $accessorCache = [];

            if (isset(self::$privatePropertiese171f[$name][$class])) {
                $cacheKey = $class . '#' . $name;
                $accessor = isset($accessorCache[$cacheKey])
                    ? $accessorCache[$cacheKey]
                    : $accessorCache[$cacheKey] = \Closure::bind(function & ($instance) use ($name) {
                        return $instance->$name;
                    }, null, $class);

                return $accessor($this);
            }

            if ($this->initializationTrackerce44e || 'ReflectionProperty' === $class) {
                $tmpClass = key(self::$privatePropertiese171f[$name]);
                $cacheKey = $tmpClass . '#' . $name;
                $accessor = isset($accessorCache[$cacheKey])
                    ? $accessorCache[$cacheKey]
                    : $accessorCache[$cacheKey] = \Closure::bind(function & ($instance) use ($name) {
                        return $instance->$name;
                    }, null, $tmpClass);

                return $accessor($this);
            }
        }

        return parent::__get($name);
    }

    public function __set($name, $value)
    {
        $this->initializerc3072 && $this->callInitializer728ea('__set', array('name' => $name, 'value' => $value));

        if (isset(self::$publicPropertiesda3cc[$name])) {
            return ($this->$name = $value);
        }

        if (isset(self::$protectedProperties1a2d1[$name])) {
            // check protected property access via compatible class
            $callers      = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
            $caller       = isset($callers[1]) ? $callers[1] : [];
            $object       = isset($caller['object']) ? $caller['object'] : '';
            $expectedType = self::$protectedProperties1a2d1[$name];

            if ($object instanceof $expectedType) {
                return ($this->$name = $value);
            }

            $class = isset($caller['class']) ? $caller['class'] : '';

            if ($class === $expectedType || is_subclass_of($class, $expectedType) || $class === 'ReflectionProperty') {
                return ($this->$name = $value);
            }
        } elseif (isset(self::$privatePropertiese171f[$name])) {
            // check private property access via same class
            $callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
            $caller  = isset($callers[1]) ? $callers[1] : [];
            $class   = isset($caller['class']) ? $caller['class'] : '';

            static $accessorCache = [];

            if (isset(self::$privatePropertiese171f[$name][$class])) {
                $cacheKey = $class . '#' . $name;
                $accessor = isset($accessorCache[$cacheKey])
                    ? $accessorCache[$cacheKey]
                    : $accessorCache[$cacheKey] = \Closure::bind(function ($instance, $value) use ($name) {
                        return ($instance->$name = $value);
                    }, null, $class);

                return $accessor($this, $value);
            }

            if ('ReflectionProperty' === $class) {
                $tmpClass = key(self::$privatePropertiese171f[$name]);
                $cacheKey = $tmpClass . '#' . $name;
                $accessor = isset($accessorCache[$cacheKey])
                    ? $accessorCache[$cacheKey]
                    : $accessorCache[$cacheKey] = \Closure::bind(function ($instance, $value) use ($name) {
                        return ($instance->$name = $value);
                    }, null, $tmpClass);

                return $accessor($this, $value);
            }
        }

        return parent::__set($name, $value);
    }

    public function __isset($name)
    {
        $this->initializerc3072 && $this->callInitializer728ea('__isset', array('name' => $name));

        if (isset(self::$publicPropertiesda3cc[$name])) {
            return isset($this->$name);
        }

        if (isset(self::$protectedProperties1a2d1[$name])) {
            // check protected property access via compatible class
            $callers      = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
            $caller       = isset($callers[1]) ? $callers[1] : [];
            $object       = isset($caller['object']) ? $caller['object'] : '';
            $expectedType = self::$protectedProperties1a2d1[$name];

            if ($object instanceof $expectedType) {
                return isset($this->$name);
            }

            $class = isset($caller['class']) ? $caller['class'] : '';

            if ($class === $expectedType || is_subclass_of($class, $expectedType)) {
                return isset($this->$name);
            }
        } else {
            // check private property access via same class
            $callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
            $caller  = isset($callers[1]) ? $callers[1] : [];
            $class   = isset($caller['class']) ? $caller['class'] : '';

            static $accessorCache = [];

            if (isset(self::$privatePropertiese171f[$name][$class])) {
                $cacheKey = $class . '#' . $name;
                $accessor = isset($accessorCache[$cacheKey])
                    ? $accessorCache[$cacheKey]
                    : $accessorCache[$cacheKey] = \Closure::bind(function ($instance) use ($name) {
                        return isset($instance->$name);
                    }, null, $class);

                return $accessor($this);
            }

            if ('ReflectionProperty' === $class) {
                $tmpClass = key(self::$privatePropertiese171f[$name]);
                $cacheKey = $tmpClass . '#' . $name;
                $accessor = isset($accessorCache[$cacheKey])
                    ? $accessorCache[$cacheKey]
                    : $accessorCache[$cacheKey] = \Closure::bind(function ($instance) use ($name) {
                        return isset($instance->$name);
                    }, null, $tmpClass);

                return $accessor($this);
            }
        }

        return parent::__isset($name);
    }

    public function __unset($name)
    {
        $this->initializerc3072 && $this->callInitializer728ea('__unset', array('name' => $name));

        if (isset(self::$publicPropertiesda3cc[$name])) {
            unset($this->$name);

            return;
        }

        if (isset(self::$protectedProperties1a2d1[$name])) {
            // check protected property access via compatible class
            $callers      = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
            $caller       = isset($callers[1]) ? $callers[1] : [];
            $object       = isset($caller['object']) ? $caller['object'] : '';
            $expectedType = self::$protectedProperties1a2d1[$name];

            if ($object instanceof $expectedType) {
                unset($this->$name);

                return;
            }

            $class = isset($caller['class']) ? $caller['class'] : '';

            if ($class === $expectedType || is_subclass_of($class, $expectedType) || $class === 'ReflectionProperty') {
                unset($this->$name);

                return;
            }
        } elseif (isset(self::$privatePropertiese171f[$name])) {
            // check private property access via same class
            $callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
            $caller  = isset($callers[1]) ? $callers[1] : [];
            $class   = isset($caller['class']) ? $caller['class'] : '';

            static $accessorCache = [];

            if (isset(self::$privatePropertiese171f[$name][$class])) {
                $cacheKey = $class . '#' . $name;
                $accessor = isset($accessorCache[$cacheKey])
                    ? $accessorCache[$cacheKey]
                    : $accessorCache[$cacheKey] = \Closure::bind(function ($instance) use ($name) {
                        unset($instance->$name);
                    }, null, $class);

                return $accessor($this);
            }

            if ('ReflectionProperty' === $class) {
                $tmpClass = key(self::$privatePropertiese171f[$name]);
                $cacheKey = $tmpClass . '#' . $name;
                $accessor = isset($accessorCache[$cacheKey])
                    ? $accessorCache[$cacheKey]
                    : $accessorCache[$cacheKey] = \Closure::bind(function ($instance) use ($name) {
                        unset($instance->$name);
                    }, null, $tmpClass);

                return $accessor($this);
            }
        }

        return parent::__unset($name);
    }

    public function __clone()
    {
        $this->initializerc3072 && $this->callInitializer728ea('__clone', []);

        parent::__clone();
    }

    public function __sleep()
    {
        $this->initializerc3072 && $this->callInitializer728ea('__sleep', []);

        return array_keys((array) $this);
    }

    public function setProxyInitializer(\Closure $initializer = null)
    {
        $this->initializerc3072 = $initializer;
    }

    public function getProxyInitializer()
    {
        return $this->initializerc3072;
    }

    public function initializeProxy() : bool
    {
        return $this->initializerc3072 && $this->callInitializer728ea('initializeProxy', []);
    }

    public function isProxyInitialized() : bool
    {
        return ! $this->initializerc3072;
    }


}

If you've got any trouble reproducing this issue I can also figure out some time to set up a minimal repo tomorrow.

@Ocramius
Copy link
Owner

Ocramius commented Oct 7, 2019

Are you running on 7.3 or 7.4? Which version of ProxyManager?

Also, that lot of code is really only hit if a property stays unset, not really otherwise (unless you are accessing it from a scope where it isn't visible from)

@Frederick888
Copy link
Author

Frederick888 commented Oct 7, 2019

I'm using PHP 7.3.10 and ProxyManager 2.2.3.

By the way, just guessing but https://github.com/doctrine/mongodb-odm/blob/2.0.0/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php#L468 might be relevant? Since if the initialiser is set to null callInitializer728ea() will never be called.
It's been there for quite some time.

        if ($document instanceof GhostObjectInterface) {
            $document->setProxyInitializer(null);
        }

@Ocramius
Copy link
Owner

Ocramius commented Oct 7, 2019

Yeah, I think I wrote that code myself. Is your proxy completely uninitialised, or is there some partial initialisation going on?

@Frederick888
Copy link
Author

Partial. Properties set by the hydrator are there but callInitializer728ea() is never called (got a breakpoint on the first line of the function and was never hit).

@Ocramius
Copy link
Owner

Ocramius commented Oct 7, 2019

Hmm, and the set initializer was never called? Or was it called at aome point?

Specifically, I'd expect unset properties to still be unset if the initializer was skipped. The initializer provided by this library, if called, will set all defined properties to their default values before calling userland logic.

@Frederick888
Copy link
Author

I noticed that this issue can only be reproduced when the object is loaded through a reference relation ship. I've put together a much simpler example at https://github.com/Frederick888/mongodb-odm-example

It's built upon a Laravel empty project and the only 4 relevant files are:

File Description
app/Doctrine/BaseModel.php Same base model as above
app/Doctrine/Tree.php References to zero or many apples
app/Doctrine/Apple.php
routes/console.php Functions to reproduce the issue

You can

$ git clone https://github.com/Frederick888/mongodb-odm-example.git
$ cd mongodb-odm-example
$ mkdir -p doctrine/proxies doctrine/hydrators
$ cp .env.example .env
$ printf 'DATABASE_MONGO=%s\n' "$MONGO_CONNECTION_STRING" | tee -a .env
$ php artisan mongo-seed
$ php artisan mongo-test0 # fail
$ php artisan mongo-test1 # success

...to see the error. (I actually wonder whether this is still a bug of ProxyManager or something missing from mongodb-odm when loading referenced documents.)


Specifically, I'd expect unset properties to still be unset if the initializer was skipped.

What did you mean by 'unset properties'? The ones that are unset in staticProxyConstructor($initializer)? If so the did remain unset and $excludedFromArray was actually one of them.

@Ocramius
Copy link
Owner

Ocramius commented Oct 8, 2019

Eh, sorry, can't really do much with an example app. The ODM has a test suite though: see https://github.com/doctrine/mongodb-odm/tree/master/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket for examples

@Ocramius
Copy link
Owner

Ocramius commented Oct 8, 2019

If so the did remain unset

Possibly the problem: the default behavior is to initialize all properties to the defaults when an initializer is called. If they stay unset, then it likely means that the ODM did something "manually" that wasn't expected.

@Frederick888
Copy link
Author

Ok, I'll figure out some time to write a test case using the suite. Btw are you able to reproduce this issue on your end right now?

@Ocramius
Copy link
Owner

Ocramius commented Oct 8, 2019

Not really - that's why I asked for an isolated test :D

@Frederick888
Copy link
Author

@Ocramius There you go :)

$ ./vendor/bin/phpunit --filter GH2080Test
PHPUnit 8.4.1 by Sebastian Bergmann and contributors.

E                                                                   1 / 1 (100%)

Time: 677 ms, Memory: 38.00 MB

There was 1 error:

1) Doctrine\ODM\MongoDB\Tests\Functional\Ticket\GH2080Test::testReferencedDocumentPropertyInitialisation
Undefined property: Proxies\__PM__\Doctrine\ODM\MongoDB\Tests\Functional\Ticket\GH2080Apple\Generateddf8b6d0ca953dfdced173aed374a86ac::$excludedFromArray

/home/frederick/Programming/PHP/mongodb-odm/tests/Proxies/Proxies__PM__DoctrineODMMongoDBTestsFunctionalTicketGH2080AppleGenerateddf8b6d0ca953dfdced173aed374a86ac.php:139
/home/frederick/Programming/PHP/mongodb-odm/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH2080Test.php:124
/home/frederick/Programming/PHP/mongodb-odm/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH2080Test.php:29

ERRORS!
Tests: 1, Assertions: 1, Errors: 1.

@Ocramius
Copy link
Owner

Ocramius commented Oct 8, 2019

@Frederick888 can you try your test against doctrine/mongodb-odm#2082 ?

@Frederick888
Copy link
Author

@Ocramius Yup it's working now! Thanks!

@Ocramius
Copy link
Owner

Ocramius commented Oct 9, 2019

Closing here then. If relevant, contribute your test to upstream ODM 👍

@Ocramius Ocramius closed this as completed Oct 9, 2019
@Ocramius Ocramius assigned Ocramius and unassigned Frederick888 Oct 9, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants