Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[Serializer] Serialize and deserialize from abstract classes#24375

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Conversation

@sroze
Copy link
Contributor

@srozesroze commentedSep 29, 2017
edited
Loading

QA
Branch?master
Bug fix?no
New feature?yes
BC breaks?no
Deprecations?no
Tests pass?yes
Fixed tickets ø
LicenseMIT
Doc PRNot yet

This PR adds a feature in the Serializer: allow to serialize and de-serialize abstract classes. Such feature is especially useful when dealing with domain objects.

Example

Let's take the example of the following objects:

  • CodeRepository defines a set of properties likename andurl
  • GitHubCodeRepository andBitBucketCodeRepository extends from the abstractCodeRepository class and adds a few properties.
  • Project has a relation with acodeRepository, which has a typeCodeRepository.

At the moment, the serializer can't serialize/deserialize correctly thisProject object has it doesn't know how to deal with thisCodeRepository abstract object.

This feature allows the serializer to deal with such situation. TheObjectNormalizer has now access to aClassDiscriminatorResolver that knows, for a given abstract class:

  • Is the "type" property it needs to read/write to uniquely identify each sub-class
  • What's the name of the "type" for each sub-class mapping

Usage without Framework Bundle

$discriminatorResolver =newClassDiscriminatorResolver();$discriminatorResolver->addClassMapping(CodeRepository::class,newClassDiscriminatorMapping('type', ['github' => GitHubCodeRepository::class,'bitbucket' => BitBucketCodeRepository::class,]));$serializer =newSerializer(array(newObjectNormalizer(null,null,null,null,$discriminatorResolver)),array('json' =>newJsonEncoder()));$serialized =$serializer->serialize(newGitHubCodeRepository());// {"type": "github"}$repository =$serializer->unserialize($serialized, CodeRepository::class,'json');// GitHubCodeRepository

Usage with the Framework Bundle

framework:serializer:discriminator_class_mapping:App\CodeRepository:type_property:typemapping:github:App\GitHubCodeRepositorybitbucket:App\BitBucketCodeRepository

Usage with Annotations/XML/YAML

useSymfony\Component\Serializer\Annotation\DiscriminatorMap;/** * @DiscriminatorMap(typeProperty="type", mapping={ *    "first"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild", *    "second"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild" * }) */abstractclass AbstractDummy{public$foo;publicfunction__construct($foo =null)    {$this->foo =$foo;    }}

TODO

  • Working as standalone
  • Working with the framework bundle
  • Tests on mapping classes

private$typeProperty;
private$typesMapping;

publicfunction__construct(string$typeProperty,array$typesMapping =array())
Copy link
Member

Choose a reason for hiding this comment

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

Should be compatible with PHP 5.4 to target 3.4.

return$this->typeProperty;
}

publicfunctionaddTypeMapping(string$type,string$typeClass)
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to remove this method? In the PropertyInfo component we used immutable objects.

/**
* @author Samuel Roze <samuel.roze@gmail.com>
*/
finalclass ClassDiscriminatorResolver
Copy link
Member

Choose a reason for hiding this comment

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

It should implement an interface as it's not a value object (it has a behavior).

/**
* {@inheritdoc}
*/
protectedfunctioninstantiateObject(array &$data,$class,array &$context,\ReflectionClass$reflectionClass,$allowedAttributes,string$format =null)
Copy link
Member

Choose a reason for hiding this comment

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

Can't you move this in the parent class? It will make it easier to support for this feature in other normalizers.

@dunglas
Copy link
Member

Great, I like it a lot. Would fit perfectly to denormalize JSON-LD documents (@type).

@srozesrozeforce-pushed theserialize-and-deserialize-from-abstract-classes branch fromc472eaa tob3835d3CompareSeptember 29, 2017 13:32
@sroze
Copy link
ContributorAuthor

@dunglas updated based on your requests and added the framework bundle bridge.

@srozesrozeforce-pushed theserialize-and-deserialize-from-abstract-classes branch fromcf3ee7d toeab4a89CompareSeptember 29, 2017 13:59
@sroze
Copy link
ContributorAuthor

Can't get the bottom of this Travis issue for PHP 7+ 🤔

*/
publicfunctionaddClassMapping($class,ClassDiscriminatorMapping$mapping)
{
if (isset($this->mapping[$class])) {
Copy link
Contributor

Choose a reason for hiding this comment

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

How about injecting the mappings through a constructor and making this verification at container compile time, not runtime?

*/
publicfunctiongetMappingForClass($class)
{
if (isset($this->mapping[$class])) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I thinkarray_key_exists can safely replaceisset here

sroze reacted with confused emoji
@theofidry
Copy link
Contributor

theofidry commentedSep 29, 2017
edited
Loading

@sroze could you provide a sample of the problematic code? I do have similar relationships in my codebase and it works, because at the end of the day the class you need to serialize is notCodeRepository butGitHubRepository orBitbucketRepository which the serializer knows how to handle.

@srozesrozeforce-pushed theserialize-and-deserialize-from-abstract-classes branch from95313de to24c1e9eCompareSeptember 29, 2017 16:20
@sroze
Copy link
ContributorAuthor

@theofidry the tricky bit is the de-serialization, it would complain about not being able to instanciate an abstract class 😉

@srozesrozeforce-pushed theserialize-and-deserialize-from-abstract-classes branch fromf779b20 to8caf193CompareSeptember 29, 2017 20:05
@jderusse
Copy link
Member

Is this feature extensible to everything? and not only abstract classes.

Would love to use$message = $serializer->unserialize($data, MessageInterface::class, 'json');
Without need to extend an abstract class nor writing a the list of existing mapping.

Mapping could be done by using the basename of the Entity classname (serialize), and reverse mapping by browsing a list of known namespaces and chaking if the class name exists.

WDYT?

@ogizanagi
Copy link
Contributor

ogizanagi commentedSep 29, 2017
edited
Loading

I didn't look at this deep, but:

  1. why do we expose bothframework.serializer.discriminator_class_mapping and yml/xml/annotations for this?
  2. why allowing to pass aClassDiscriminatorResolverInterface toObjectNormalizer instead of relying on the metadata already registered?
  3. What about allowing to call a resolver instead of a property?

I agree this feature could be useful not only with abstract classes.

/**
* @author Samuel Roze <samuel.roze@gmail.com>
*/
finalclass ClassDiscriminatorMapping

Choose a reason for hiding this comment

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

we don't make classes "final" by default, eg for data objects. Is it the case here? If there is no special reason, I'd suggest to remove them.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, this is a data object. Happy to remove thefinal even tho I believe closed-first is the best approach 😜

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

@nicolas-grekas removed the final 👍

@sroze
Copy link
ContributorAuthor

Would love to use $message = $serializer->unserialize($data, MessageInterface::class, 'json');

@jderusse yes, good point, I'll do this.

Mapping could be done by using the basename of the Entity classname (serialize), and reverse mapping by browsing a list of known namespaces and chaking if the class name exists.

That's a very good point as well, this could potentially be automated using aclass name => class name mapping. But I believe it would be better to start with an explicit configuration as I wouldn't personally advise to use class names within serialised documents. If we see people having no problem with this and see the DX can be drastically improved this way, why not :)

why do we expose both framework.serializer.discriminator_class_mapping and yml/xml/annotations for this?
why allowing to pass a ClassDiscriminatorResolverInterface to ObjectNormalizer instead of relying on the metadata already registered?

@ogizanagi The reason is that I started by exposing thediscriminator_class_mapping service but@dunglas told me he would prefer to have some configuration looking like the existing one. I have kept both as I believe it gives us a greater configuration flexibility but happy to keep only one.

What about allowing to call a resolver instead of a property?

@ogizanagi I don't get the question.

@srozesrozeforce-pushed theserialize-and-deserialize-from-abstract-classes branch fromd668d4d to4a37a02CompareSeptember 30, 2017 12:17
@sroze
Copy link
ContributorAuthor

Serializer's functional tests are the only one failing, with an exception with the newly introduced class:

1) Symfony\Bundle\FrameworkBundle\Tests\Functional\SerializerTest::testDeserializeArrayOfObjectError: Class 'Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata' not found

Any idea why@dunglas@nicolas-grekas ?

private$classMetadataFactory;

/**
* @param ClassMetadataFactoryInterface $classMetadataFactory
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for this phpdoc

<serviceid="serializer.property_accessor"alias="property_accessor" />

<!-- Discriminator Map-->
<serviceid="serializer.mapping.class_discriminator_resolver"class="Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata">
Copy link
Member

Choose a reason for hiding this comment

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

This service must beprivate.
Can you aliasserializer.mapping.class_discriminator_resolver toClassDiscriminatorResolverInterface. It will allow to use autowiring.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

It is private, as it's in thedefaults. Added the alias.

dunglas reacted with thumbs up emoji
*/
publicfunction__construct(array$data)
{
if (!isset($data['typeProperty']) || !$data['typeProperty']) {
Copy link
Member

Choose a reason for hiding this comment

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

isset is faster andnull should throw an exception isn't it?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Yes,null or a falsy contents should throw an exception, henceisset.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is what'sempty made for

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

empty fails if the key do not exists.

Copy link
Contributor

Choose a reason for hiding this comment

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

@sroze : Hmm, no it doesn't 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

What I meant:

http://php.net/manual/en/function.empty.php

empty() does not generate a warning if the variable does not exist.

Copy link
Member

Choose a reason for hiding this comment

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

fromhttp://php.net/manual/en/function.empty.php

No warning is generated if the variable does not exist. That means empty() is essentially the concise equivalent to !isset($var) || $var == false.

seehttps://3v4l.org/24O2e

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Usedempty so we are all happy. Not sure about?? within aif and throwing an exception...

$this->typeProperty =$data['typeProperty'] ??thrownew ...;

??

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Oh. I was sure it was warninging, thanks ❤️

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Used it and added tests that proves it works 👍

thrownewInvalidArgumentException(sprintf('Parameter "typeProperty" of annotation "%s" cannot be empty.',get_class($this)));
}

if (!isset($data['mapping']) ||empty($data['mapping'])) {
Copy link
Member

Choose a reason for hiding this comment

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

you can changeempty to! like in the previous test (or just useempty and remove the call toisset).

$metadata =$this->classMetadataFactory->getMetadataFor($object);

if (null !==$metadata->getClassDiscriminatorMapping()) {
return$metadata->getClassDiscriminatorMapping();
Copy link
Member

Choose a reason for hiding this comment

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

Storing the result in a local cache will improve performance (especially in the case where reflection is involved).

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Good point, added local cache bellow 👍 (the class metadata factory is already cached)

*/
publicfunctiongetTypeForMappedObject($object)
{
if (null === ($mapping =$this->getMappingForMappedObject($object))) {
Copy link
Member

Choose a reason for hiding this comment

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

Parenthesis can be removed.

parent::__construct($classMetadataFactory,$nameConverter);

$this->propertyTypeExtractor =$propertyTypeExtractor;
$this->classDiscriminatorResolver =$classDiscriminatorResolver ?: (null !==$classMetadataFactory ?newClassDiscriminatorFromClassMetadata($classMetadataFactory) :null);
Copy link
Member

Choose a reason for hiding this comment

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

We try to avoid imbricating ternaries (for readability). Can you switch to aif construct?

protectedfunctiongetAttributeValue($object,$attribute,$format =null,array$context =array())
{
if ($this->classDiscriminatorResolver &&$mapping =$this->classDiscriminatorResolver->getMappingForMappedObject($object)) {
if ($attribute ==$mapping->getTypeProperty()) {
Copy link
Member

Choose a reason for hiding this comment

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

You can merge bothif right?


publicfunctiontestLoadDiscriminatorMap()
{
$classMetadata =newClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\AbstractDummy');
Copy link
Member

Choose a reason for hiding this comment

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

You can useAbstractDummy::class here too (recommended for new code)

@@ -0,0 +1,15 @@
<?php
Copy link
Member

Choose a reason for hiding this comment

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

Those files should me removed now isn't it?

@srozesrozeforce-pushed theserialize-and-deserialize-from-abstract-classes branch from9e12d44 tod21d2f9CompareOctober 1, 2017 16:53
@sroze
Copy link
ContributorAuthor

@jderusse this is now working with interfaces as well.
@ogizanagi I've removed the configuration via the FrameworkBundle, only kept annotations/xml/yaml.
@dunglas added the resolver local cache and changed the CS.

ogizanagi reacted with thumbs up emoji

@sroze
Copy link
ContributorAuthor

Test errors are normal as the functional tests are usingdev-master components and therefore don't get the class introduced and used while creating the serializer. Should be ready 🤗

*/
protected$classDiscriminatorResolver;

publicfunction__construct(ClassMetadataFactoryInterface$classMetadataFactory =null,NameConverterInterface$nameConverter =null,PropertyTypeExtractorInterface$propertyTypeExtractor =null,ClassDiscriminatorResolverInterface$classDiscriminatorResolver =null)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we really want to keep exposing this$classDiscriminatorResolver argument? Configuring everything through metadata looks enough to me, isn't it?

afurculita reacted with thumbs up emoji
Copy link
Member

@dunglasdunglasOct 1, 2017
edited
Loading

Choose a reason for hiding this comment

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

I would keep it. We may leverage this extension point with API Platform to remove a lot of code related toabstract classes handling. But we need to hook our own metadata system.

@fabpot
Copy link
Member

Moving this one to 4.1. There is no need to rush out such a new feature.

@fabpotfabpot added this to the4.1 milestoneOct 1, 2017
@srozesroze changed the base branch from3.4 tomasterOctober 2, 2017 07:10
@sroze
Copy link
ContributorAuthor

@fabpot changed the PR to be based onmaster and used PHP 7 things. The bot isn't happy about PHP7 yet tho 😉

* @var ClassMetadataFactoryInterface
*/
private$classMetadataFactory;
private$mappingForMappedObjectCache = [];
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the short syntax for arrays has been accepted. Php-CS-fixer still has the long syntax specified in the config file

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

But if we target PHP7+ we need to accept this :)

Copy link
Contributor

@afurculitaafurculitaOct 2, 2017
edited
Loading

Choose a reason for hiding this comment

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

I agree with you. But fabbot is still complaining. I think this rule should be removed from php_cs.dist and from fabbot, to permit new developments to use the short syntax. Would you like to open an issue / PR about this? At this point, according to this PR#22862, they try to avoid merge conflicts.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

#24396 👍

* @param string $typeProperty
* @param array $typesMapping
*/
publicfunction__construct($typeProperty,array$typesMapping = [])
Copy link
Contributor

Choose a reason for hiding this comment

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

string can be added as typehint and the phpdoc removed

sroze reacted with thumbs up emoji
*
* @return ClassDiscriminatorMapping|null
*/
publicfunctiongetMappingForClass(string$class);
Copy link
Contributor

Choose a reason for hiding this comment

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

A return type can be specified for each method

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

We are targeting PHP 7.0, not 7.1, right? Can't add the return type when it can returnnull then 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

Symfony 4 supports PHP >= 7.1.3

sroze reacted with hooray emoji
@srozesrozeforce-pushed theserialize-and-deserialize-from-abstract-classes branch 2 times, most recently from72ee16a to5a7f984CompareOctober 23, 2017 09:59
@srozesrozeforce-pushed theserialize-and-deserialize-from-abstract-classes branch from5a7f984 toa83ad3fCompareNovember 24, 2017 12:48
Add the `discriminator_class_mapping` in the framework extensionDiscriminator map is compatible with interfaces
@srozesrozeforce-pushed theserialize-and-deserialize-from-abstract-classes branch from5271a72 to4c70e94CompareDecember 3, 2017 11:55
@sroze
Copy link
ContributorAuthor

Rebased onmaster & squashed all the commits. Failure onDEPS=low is expected until we merge it.

For you @symfony/deciders 👍

@nicolas-grekas
Copy link
Member

A failure on deps=low is almost never legitimate. Should be doubly checked.

@sroze
Copy link
ContributorAuthor

@nicolas-grekas a new class used by FrameworkBundle's configuration (in the declared services). Should we add it only when the class exists or something? 🤔

@nicolas-grekas
Copy link
Member

class_exists if possible is best, instead of bumping the minimum version

@sroze
Copy link
ContributorAuthor

sroze commentedDec 3, 2017
edited
Loading

Good shout, just made theFrameworkBundle able to deal without the class 👍

And it's green now. 💚

@fabpot
Copy link
Member

Thank you@sroze.

@fabpotfabpot closed thisDec 7, 2017
fabpot added a commit that referenced this pull requestDec 7, 2017
…lasses (sroze)This PR was squashed before being merged into the 4.1-dev branch (closes#24375).Discussion----------[Serializer] Serialize and deserialize from abstract classes| Q             | A| ------------- | ---| Branch?       | master| Bug fix?      | no| New feature?  | yes| BC breaks?    | no| Deprecations? | no| Tests pass?   | yes| Fixed tickets | ø| License       | MIT| Doc PR        | Not yetThis PR adds a feature in the Serializer: allow to serialize and de-serialize abstract classes. Such feature is especially useful when dealing with domain objects.# ExampleLet's take the example of the following objects:- `CodeRepository` defines a set of properties like `name` and `url`- `GitHubCodeRepository` and `BitBucketCodeRepository` extends from the abstract `CodeRepository` class and adds a few properties.- `Project` has a relation with a `codeRepository`, which has a type `CodeRepository`.At the moment, the serializer can't serialize/deserialize correctly this `Project` object has it doesn't know how to deal with this `CodeRepository` abstract object.This feature allows the serializer to deal with such situation. The `ObjectNormalizer` has now access to a `ClassDiscriminatorResolver` that knows, for a given abstract class:- Is the "type" property it needs to read/write to uniquely identify each sub-class- What's the name of the "type" for each sub-class mapping# Usage without Framework Bundle```php$discriminatorResolver = new ClassDiscriminatorResolver();$discriminatorResolver->addClassMapping(CodeRepository::class, new ClassDiscriminatorMapping('type', [    'github' => GitHubCodeRepository::class,    'bitbucket' => BitBucketCodeRepository::class,]));$serializer = new Serializer(array(new ObjectNormalizer(null, null, null, null, $discriminatorResolver)), array('json' => new JsonEncoder()));$serialized = $serializer->serialize(new GitHubCodeRepository());// {"type": "github"}$repository = $serializer->unserialize($serialized, CodeRepository::class, 'json');// GitHubCodeRepository```# Usage with the Framework Bundle```yamlframework:    serializer:        discriminator_class_mapping:            App\CodeRepository:                 type_property: type                 mapping:                    github: App\GitHubCodeRepository                    bitbucket: App\BitBucketCodeRepository```# Usage with Annotations/XML/YAML```phpuse Symfony\Component\Serializer\Annotation\DiscriminatorMap;/** * @DiscriminatorMap(typeProperty="type", mapping={ *    "first"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild", *    "second"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild" * }) */abstract class AbstractDummy{    public $foo;    public function __construct($foo = null)    {        $this->foo = $foo;    }}```# TODO- [x] Working as standalone- [x] Working with the framework bundle- [x] Tests on mapping classesCommits-------4c6e05b [Serializer] Serialize and deserialize from abstract classes
@sroze
Copy link
ContributorAuthor

sroze commentedDec 7, 2017 via email

I think we should do a blog post for this one. Javier, should we pair onthat? :)
On Thu, 7 Dec 2017 at 18:34, Fabien Potencier ***@***.***> wrote: Closed#24375 <#24375>. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#24375 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AAxHEXIVzy6LpTuTYyDEMUms0smEgFMpks5s-C_DgaJpZM4Pomri> .

@srozesroze deleted the serialize-and-deserialize-from-abstract-classes branchDecember 8, 2017 09:45
javiereguiluz added a commit to symfony/symfony-docs that referenced this pull requestJan 9, 2018
…n of interfaces and abstract classes (sroze, javiereguiluz)This PR was merged into the master branch.Discussion----------[Serializer] Add documentation about the (de)serialization of interfaces and abstract classesFixes#9011Add documentation forsymfony/symfony#24375Commits-------ac831b0 Minor fixes and some tweaks61b9c53 Add documentation about the (de)serialization of interfaces and abstract classes
@fabpotfabpot mentioned this pull requestMay 7, 2018
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@nicolas-grekasnicolas-grekasnicolas-grekas left review comments

@jderussejderussejderusse left review comments

@fabpotfabpotfabpot approved these changes

@dunglasdunglasdunglas approved these changes

+3 more reviewers

@ostroluckyostroluckyostrolucky left review comments

@ogizanagiogizanagiogizanagi left review comments

@afurculitaafurculitaafurculita left review comments

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Projects

None yet

Milestone

4.1

Development

Successfully merging this pull request may close these issues.

10 participants

@sroze@dunglas@theofidry@jderusse@ogizanagi@fabpot@nicolas-grekas@ostrolucky@afurculita@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp