Add support for enum to Utils::stringifyValue in order to support enum as default value of class property
(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:
- this approach would only work with backed enums and not basic enums.
-
graphql:dump-schemacommand 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
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;