8000 value object as ApiResource by komik966 · Pull Request #1843 · api-platform/core · GitHub
[go: up one dir, main page]

Skip to content

value object as ApiResource #1843

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

Closed
wants to merge 1 commit into from

Conversation

komik966
Copy link
Contributor
@komik966 komik966 commented Apr 11, 2018
Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? no
Fixed tickets n/a
License MIT
Doc PR n/a

I'm aware that this PR still needs work, but I opened it to discuss about value objects as ApiResource.
To get the idea check behat test: https://github.com/api-platform/core/compare/master...komik966:vo-denormalizer?expand=1#diff-6af974d3c9848f846d1ee1e58d88564a and entity: https://github.com/api-platform/core/compare/master...komik966:vo-denormalizer?expand=1#diff-e1fa2952eafb66484a8db3d626ddba31
If you agree with it I'll do further work.

Found (potential) bug:
I've not hardly investigated it but it seems that PurgeHttpCacheListener has bug - i tries to get entity id before one is created, but not in every case - it not works on entities which I've added in this PR (VoDummy*). For now I commented the line which causes issue (https://github.com/api-platform/core/compare/master...komik966:vo-denormalizer?expand=1#diff-07581ee0a08a8e5193cb8a7ec890c3b7R83)

Questions:

TODOS:

@mysiar mysiar requested review from dunglas and meyerbaptiste April 12, 2018 05:54
@komik966
Copy link
Contributor Author

symfony/symfony#25605

@komik966
Copy link
Contributor Author

#1749

@bendavies
Copy link
Contributor

Very nice. This is what I'd be looking to use

}

$constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes);
if ($constructor) {
Copy link
Contributor

Choose a reason for hiding this comment

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

you can inline this

if ($constructor) {
$constructorParameters = $constructor->getParameters();

$params = array();
Copy link
Contributor

Choose a reason for hiding this comment

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

you can use short array syntax

}

/**
* @throws ReflectionException
Copy link
Contributor

Choose a reason for hiding this comment

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

we do not document exception not thrown directly by the method


class ConstructorExtractor implements PropertyAccessExtractorInterface
{
public function isReadable($class, $property, array $context = array())
Copy link
Contributor

Choose a reason for hiding this comment

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

you can use short array syntax

And the JSON should be equal to:
"""
{
"@context": "\/contexts\/VoDummyCar",
Copy link
Contributor

Choose a reason for hiding this comment

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

you should'nt need to escape the json

@gorghoa
Copy link
8000 gorghoa commented Apr 13, 2018

from my POV, sounds like a great feature, consistent with doctrine’s recommendation on using rich entities (https://www.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/getting-started.html#adding-behavior-to-entities)

use ReflectionException;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;

class ConstructorExtractor implements PropertyAccessExtractorInterface
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess yes, this could be contributed directly in symfony, but before doing it, please wait for @dunglas POV.

Copy link
Contributor
@Nek- Nek- Apr 13, 2018

Choose a reason for hiding this comment

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

I don't think so. The fact that a parameter is writable is driven by the fact that you can write it for an instantiated object. So this would probably never be inside the Symfony component.

Copy link
Member

Choose a reason for hiding this comment

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

I tend to agree, this a low level and common need, it should be in Symfony, not in API Platform (history: we moved the code PropertyInfo code from API Platform to a standalone library then to Symfony for this exact same reason).

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author
@komik966 komik966 Apr 21, 2018

Choose a reason for hiding this comment

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

Adding property_info.initializable_extractor tag is better than using property_info.access_extractor, I like this approach. It also solves the problem which @Nek- pointed out

$key = $this->nameConverter ? $this->nameConverter->normalize($paramName) : $paramName;

$allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes);
$ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context);
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this could be directly called as $ignored, or renamed ?

because following the codes path you are using ! two times on this variable.

$allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes);
$ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context);
if ($constructorParameter->isVariadic()) {
if ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

why using both isset and array_key_exists?

Copy link
Member

Choose a reason for hiding this comment

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

For performance I guess (isset is faster)

Copy link
Contributor
@Simperfit Simperfit left a comment

Choose a reason for hiding this comment

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

This is really nice ! Thanks for contributing it to api-platform.

What was the main reason to develop this, I'm curious ?

I've left some comments.

@komik966
Copy link
Contributor Author

Thanks for review, I'll fix it.
The reason was the need for properties without setters - in some cases it simplifies our domain model.

@komik966
Copy link
Contributor Author

@Simperfit fixed, I leaved AbstractItemNormalizer::instantiateObject as is before we decide what to do with it (it is almost copied from https://github.com/symfony/serializer/blob/4.0/Normalizer/AbstractNormalizer.php#L312)

use ReflectionException;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;

class ConstructorExtractor implements PropertyAccessExtractorInterface
Copy link
Member

Choose a reason for hiding this comment

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

I tend to agree, this a low level and common need, it should be in Symfony, not in API Platform (history: we moved the code PropertyInfo code from API Platform to a standalone library then to Symfony for this exact same reason).

$allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes);
$ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context);
if ($constructorParameter->isVariadic()) {
if ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) {
Copy link
Member

Choose a reason for hiding this comment

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

For performance I guess (isset is faster)

if ($constructorParameter->isVariadic()) {
if ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) {
if (!\is_array($data[$paramName])) {
throw new RuntimeException(sprintf('Cannot create an instance of %s from serialized data because the variadic parameter %s can only accept an array.',
Copy link
Member

Choose a reason for hiding this comment

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

Can you add quotes around values in the message?

{
$constructor = (new ReflectionClass($class))->getConstructor();

if(!$constructor) {
Copy link
Member

Choose a reason for hiding this comment

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

You should test if the class is instantiable.

}

$constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes);
if ($constructor) {
Copy link
Member

Choose a reason for hiding this comment

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

if (!$constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes)) {
    return new $class();
}

@komik966
Copy link
Contributor Author

almost done in #1749

@komik966 komik966 closed this May 30, 2018
fabpot added a commit to symfony/symfony that referenced this pull request Sep 4, 2018
… is initializable (dunglas)

This PR was squashed before being merged into the 4.2-dev branch (closes #26997).

Discussion
----------

[PropertyInfo] Add an extractor to guess if a property is initializable

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | n/a   <!-- #-prefixed issue number(s), if any -->
| License       | MIT
| Doc PR        | todo

When dealing with value objects, being able to detect if a property can be initialized using the constructor is a very valuable information. It's mandatory to add a proper value object support in API Platform and in the Serializer component.

See api-platform/core#1749 and api-platform/core#1843 for the related discussions, extended use cases and proof of concepts.

This PR adds a new interface to guess if a property can be initialized through the constructor, and an implementation using the reflection (in `ReflectionExtractor`).

Commits
-------

9d2ab9e [PropertyInfo] Add an extractor to guess if a property is initializable
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.

7 participants
0