symfony / symfony-docs

The Symfony documentation
https://symfony.com/doc
Other
2.14k stars 5.1k forks source link

Improve the TypeInfo docs #20014

Open javiereguiluz opened 1 week ago

javiereguiluz commented 1 week ago

In #19554 we merged the contribution that @Korbeil made to document the new TypeInfo component.

After merging it, I have some questions about it:


(1) When/how do you use the feature to build types? We show examples like these:

use Symfony\Component\TypeInfo\Type;

Type::int();
Type::nullable(Type::string());
Type::generic(Type::object(Collection::class), Type::int());

I can't understand how would you use that in a real PHP application. Can anyone show a real example? Thanks!


(2) In the second example, we show how to do $typeResolver = TypeResolver::create(); to get type information from an existing PHP class. But, we don't show any PHP class, so the examples feel a bit abstract to me.

Would anyone please volunteer to provide a good PHP class example that matches those code examples? Thanks!


(3) This component also supports "raw string resolving", but it's lacking a good example of the PHP class with those PHPDoc contents and the needed code to get the type information. Thanks!

Korbeil commented 6 days ago

To answer your questions:

This is not a component you will use in a Symfony app (1). This is a component made for other components or for libraries to use. For example it would be used within PropertyInfo, Serializer or some external libraries like jolicode/automapper. But I don't see a usage in a web PHP application.

For all the cases I listed, this will be used to describe metadata about PHP classes within theses libraries. Let's take the Symfony's Serializer as an example:

readonly class UserValueObject
{
  public function(
    public string $name,
    public int $age,
  ) {
  }
}

$serializer = new Serializer(...); // coming from DI
$object = $serializer->deserialize($request->getContent(), UserValueObject::class, 'json');

In this case you want to deserialize a request body into a given UserValueObject. To make this happen, the Serializer will convert json to array format (decoding part of the Serializer) then will transform array to UserValueObject thanks to denormalization. During that denormalization we will use the ObjectNormalizer witch needs to gather metadata about properties within the class it is denormalizing. Today we do it thanks to PropertyInfo PropertyTypeExtractorInterface:

interface PropertyTypeExtractorInterface
{
    /**
     * Gets types of a property.
     *
     * @return Type[]|null
     */
    public function getTypes(string $class, string $property, array $context = []);
}

There is some drawbacks about this method (which we covered in a talk Mathias and I gave in recent Symfony event: https://live.symfony.com/account/replay/video/977) and this is to improve this metadata we created TypeInfo. TypeInfo makes it possible to handle more complex types (in opposition to PropertyInfo actual extractors) so libraries like Serializer can have more rich metadata.

I'm not sure we can provide a "good PHP class example" (2) like you said because of what I explained earlier, this is not something designed to be used by everybody and made for internals mostly.

About "raw string resolving" (3) we need a bit more code to have a working example, let's make a simple ValueObject:

readonly class UserValueObject
{
  /**
   * @param string[] $addresses
   */
  public function(
    public string $name,
    public array $addresses,
  ) {
  }
}

Then we extract the phpDoc we want to parse:

$reflectionClass = new \ReflectionClass(UserValueObject::class);
$reflectionConstructor = $reflectionClass->getConstructor();
$rawDocNode = $reflectionConstructor->getDocComment();

// all of theses classes are coming from `phpstan/phpdoc` library
$phpDocParser = new PhpDocParser(new TypeParser(new ConstExprParser()), new ConstExprParser());
$tokens = new TokenIterator($this->lexer->tokenize($rawDocNode));
$phpDocNode = $this->phpDocParser->parse($tokens);
$phpDocNode = $tokens->consumeTokenType(Lexer::TOKEN_END);
$phpDocParam = $phpDocNode->getTagsByName('@param');

The previous code was taken from PhpStanExtractor that is within the PropertyInfo component.

And we pass it to the TypeResolver within TypeInfo so it can tell us what this phpDoc is about:

$typeResolver = TypeResolver::create();
dd($typeResolver->resolve($phpDocParam->value));
// will print:
// ^ Symfony\Component\TypeInfo\Type\CollectionType^ {#2435
//  -type: Symfony\Component\TypeInfo\Type\BuiltinType^ {#2436
//    -typeIdentifier: Symfony\Component\TypeInfo\TypeIdentifier^ {#505
//      +name: "STRING"
//      +value: "string"
//    }
//  }
//  -isList: false
// }

Hope this answered all your questions @javiereguiluz, please ask me if you miss any details.