php-code-generator icon indicating copy to clipboard operation
php-code-generator copied to clipboard

Add support for enum to Utils::stringifyValue in order to support enum as default value of class property

Open sustmi opened this issue 1 year ago • 1 comments

(Sorry for explaining the PR by example with another project but I do not use php-code-generator directly.) EDIT: Check my comment https://github.com/murtukov/php-code-generator/pull/21#issuecomment-2473881537 below for much simpler example.

I am using graphql:compile command from overblog/graphql-bundle (https://github.com/overblog/GraphQLBundle/blob/master/docs/index.md) which internally uses php-code-generator.

When I ran composer graphql:compile I got the following error:

[Exception]                                                                       
Cannot stringify object of class: 'FrontBundle\Enum\Profile\ProfileOutlinkType'.  

Exception trace:
  at vendor/murtukov/php-code-generator/src/Utils.php:113
  ...
  Murtukov\PHPCodeGenerator\PhpFile->save() at vendor/overblog/graphql-bundle/src/Generator/TypeGenerator.php:93

I had GraphQL types defined as follows:

namespace MyApp;

use Overblog\GraphQLBundle\Annotation as GQL;

/**
 * @GQL\Enum(name="LinkType")
 */
enum LinkType: string 
{
    case INSTAGRAM = 'instagram';
    case YOUTUBE = 'youtube';
}

/**
 * @GQL\Input(name="LinkData")
 */
class LinkData
{
    /**
     * @GQL\Field(type="LinkType!")
     */
    public LinkType $type = LinkType::INSTAGRAM;
    public string $url = '';
}

The CompileCommand tried to generate a PHP file that looks like this:

<?php

namespace Project\Generated\__DEFINITIONS__;

use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\Type;
use Overblog\GraphQLBundle\Definition\ConfigProcessor;
use Overblog\GraphQLBundle\Definition\GraphQLServices;
use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface;
use Overblog\GraphQLBundle\Definition\Type\GeneratedTypeInterface;

/**
 * THIS FILE WAS GENERATED AND SHOULD NOT BE EDITED MANUALLY.
 */
final class LinkDataType extends InputObjectType implements GeneratedTypeInterface, AliasedInterface
{
    public const NAME = 'LinkData';
    
    public function __construct(ConfigProcessor $configProcessor, GraphQLServices $services)
    {
        $config = [
            'name' => self::NAME,
            'fields' => fn() => [
                'type' => [
                    'type' => fn() => Type::nonNull($services->getType('LinkType')),
                    'defaultValue' => \MyApp\LinkType::INSTAGRAM,
                ],
                'url' => [
                    'type' => Type::nonNull(Type::string()),
                    'defaultValue' => '',
                ],
            ],
        ];
        
        parent::__construct($configProcessor->process($config));
    }
    
    /**
     * {@inheritdoc}
     */
    public static function getAliases(): array
    {
        return [self::NAME];
    }
}

however, without the changes in this PR it was failing when generating the line:

'defaultValue' => \MyApp\LinkType::INSTAGRAM,

because \Murtukov\PHPCodeGenerator\Utils::stringifyValue was trying to call __toString method on the enum instance \MyApp\LinkType::INSTAGRAM which is not possible because enums do not have (and cannot have) __toString method.

I realized that when calling stringifyValue with enum instance, we can simply return a fully-qualified reference to the enum value itself. So that is exactly what this PR does.

Note

I was also thinking about returning the string value of the enum, but:

  1. this approach would only work with backed enums and not basic enums.
  2. graphql:dump-schema command was throwing this error:
[GraphQL\Error\SerializationError]                                                                              
Cannot serialize value "instagram" as it must be an instance of enum MyApp\LinkType. 

Exception trace:
  at vendor/overblog/graphql-bundle/src/Definition/Type/PhpEnumType.php:117

sustmi avatar Nov 13 '24 14:11 sustmi

OK, I realized that using php-code-generator is pretty simple so here is a much simpler example that does not use overblog/graphql-bundle of a use-case that this PR fixes:

<?php

require_once __DIR__ . '/vendor/autoload.php';

use Murtukov\PHPCodeGenerator\Modifier;
use Murtukov\PHPCodeGenerator\PhpFile;

enum LinkType: string
{
    case INSTAGRAM = 'instagram';
    case YOUTUBE = 'youtube';
}

$file = PhpFile::new()->setNamespace('App\Generator');

$file
    ->createClass('LinkData')
    ->addProperty('url', Modifier::PUBLIC, 'string')
    ->addProperty(
        name: 'type',
        type: LinkType::class,
        // Using enum instance as default value causes error without the changes in this pull-request
        defaulValue: LinkType::INSTAGRAM,
    );

echo $file;

sustmi avatar Nov 13 '24 15:11 sustmi