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

[RFC] Treat some ArrayAccess classes like array or stdClass #144

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

GromNaN
Copy link
Contributor

@GromNaN GromNaN commented May 16, 2024

This is not a final implementation, just the way to expose the issue.

When querying MongoDB, documents are returned as MongoDB\BSON\Document. This generic class implements ArrayAccess, it can contain any field as an array or stdClass.

In order to support denormalization from MongoDB Documents to class, I need to modify the code of the library to add exception on top of array and stdClass.
Instead of being specific to MongoDB classes or wide to all ArrayAccess implementation, a config is necessary to set the list of classes to handle as arrays.

Why would this be interesting when you can convert documents to array? Because with MongoDB\BSON\Document, field values are read only when they are accessed.

  • Add tests
  • Add doc

@GromNaN GromNaN changed the title [RFC] Treat MongoDB documents as not exposing properties [RFC] Treat some ArrayAccess classes like array or stdClass May 17, 2024
@GromNaN GromNaN force-pushed the mongodb branch 8 times, most recently from 46e6070 to 6c9020c Compare May 17, 2024 08:16
Copy link
Member

@Korbeil Korbeil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems very good to me 👍

Would it possible to add a test with ArrayObject and/or one of the MongoDB class ? 🤔

src/Configuration.php Outdated Show resolved Hide resolved
src/Configuration.php Outdated Show resolved Hide resolved
@joelwurtz
Copy link
Member

joelwurtz commented May 17, 2024

I like the idea of supporting ArrayAccess.

However, It may be useful to handle all array access classes without having to define all possible classes by doing the following thing :

  • If a target implements \ArrayAccess (is_a) and a source property is not found in it we use the array access methods to write it
  • If a source implements \ArrayAccess (is_a) and a target property is not found in it we try to read from the array access

Then we could do something like :

class Foo
{
    public string $foo = 'foo'; 
    
    public string $bar = 'bar';
}

class Bar extends \ArrayObject
{
    public string $foo;
    
    // ...
}

$automapper->map(new Foo(), Bar::class));

var_dump($bar->foo); // foo
var_dump($bar['bar']); // bar

This will map the foo field and the bar field will be in the array storage

There will be no need to defined a list of class (however we could implement this feature as an opt-in so we don't break existing code that use ArrayAccess)

I believe this should work with Document of MongoDB extension also

@GromNaN
Copy link
Contributor Author

GromNaN commented May 17, 2024

That would provide a partial list of supported properties as source or target, which something not supported currently, that raise a lot of questions.
Do you have an example of such class that would have properties as class attribute and using ArrayAccess. That seems like a very bad design.

I'm using a list of supported classes because some Model classes could implementation ArrayAccess in order to serve their actual properties.

@joelwurtz
Copy link
Member

Do you have an example of such class that would have properties as class attribute and using ArrayAccess. That seems like a very bad design.

I know that some apis or others lib provide this design in order to support extra attributes / data into an object, as an example symfony provide that for their Event

I'm using a list of supported classes because some Model classes could implementation ArrayAccess in order to serve their actual properties.

We could avoid using those classes by using the ignore property of the MapTo / MapFrom attribute so don't think it's a problem

@GromNaN
Copy link
Contributor Author

GromNaN commented May 17, 2024

I'm afraid it won't work. ArrayObject has properties that are not part of the data:

$extractor = new Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor();
$properties = $extractor->getProperties(\ArrayObject::class);
// array:4 [
//   0 => "arrayCopy"
//   1 => "flags"
//   2 => "iterator"
//   3 => "iteratorClass"
// ]

Same for MongoDB\BSON\Document:

$extractor = new Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor();
$properties = $extractor->getProperties(\MongoDB\BSON\Document::class);
// array:4 [
//   0 => "iterator"
// ]

We don't want them to conflict with actual values provided using ArrayAccess. And we never want this properties to be used.

Copy link
Contributor Author

@GromNaN GromNaN left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated: Mapping an ArrayAccess to ArrayAccess|array|stdClass is not supported (same as array to array)

Comment on lines -112 to -115
if (!$metadata->isTargetUserDefined()) {
return [];
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why there is it condition. It was introduced by 1486a85#diff-538038bd35959095e72795c2a102193df6a1ee0c17b27dd7f129e23d8f2464ecR111-R113
This prevent instanciating new ArrayObject.

@GromNaN
Copy link
Contributor Author

GromNaN commented Jun 4, 2024

Given this RFC, which challenges ArrayAccess, it might be better not to rely on it.

@joelwurtz
Copy link
Member

Sorry for the late answer, i was struggling on what's best here and i think i have a proper solution.

We still want in the future to allow a user to write something like this :

class MyConfigurationObject implements ArrayAccess
{
    public $foo;

    // ... implementation
}

And the mapper would map foo and extra properties if available.

However, like you said, in some cases we don't want to extract some properties, although there is the possibility to ignore them, it's may be difficult to add when it's an external class.

The overall goal would be to allow to set a strategy for a source and target like this:

  • class strategy : extract properties with property info
  • array strategy : add properties not present in source/target from target/source by using array access
  • dynamic strategy : add properties not present in source/target from target/source by using property access (like class implementing __get / __set or \stdClass)

Strategies are cumulable (except for having array + dynamic), so someone can tell this class should be class + array strategies, or only array strategy, etc ...

We would also provide sane defaults:

  • User class => class strategy
  • User class + ArrayAccess => class + array strategies
  • Internal class + ArrayAccess => array strategy (this should cover your use case since i believe MongoDB classes are internal coming from an extension)
  • ...

I don't want to force you to write all of this, are you ok if we merge this after the next release and rework it to fit our vision ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants