Repository: symplify/symplify (php)

Issue:
[ConfigTransformer] `FQCN::method` static factories are transformed to syntax error
Initially reported [here](https://twitter.com/_Codito_/status/1561727336281686018). I was able to find that such YAML usage results with syntax error in PHP format:

```yml
services:
  foo:
    factory: Foo\Bar::baz
```

```diff
- ->factory([service(Bar::class),·'baz']);
+ ->factory([Bar::baz]);

```

Basically output file has syntax error because there is no such thing like `Bar::baz`. On the other hand, `Foo\Bar::baz` works properly in YAML, it's resolved to proper class/method.

## Expected behavior

Factories' definitions are transformed properly, without syntax error.

Pull request description:
[ConfigTransformer] Support for `FQCN::method` format in factories
Fixes #4368

I was able to reproduce the problem, but I need help with fixing it 🙂 Could you point me where to look at just to be able to handle splitting by `::` and dumping proper factory with `::class` usage?

FYI: It can be tested locally with `vendor/bin/phpunit --testdox packages/config-transformer/tests/Converter/ConfigFormatConverter/YamlToPhp/YamlToPhpTest.php --filter=testNormal#39` (runs only failing fixture).

Patch:
diff --git a/packages/php-config-printer/src/NodeFactory/ConstantNodeFactory.php b/packages/php-config-printer/src/NodeFactory/ConstantNodeFactory.php
index bc207176f..d43bfe718 100644
--- a/packages/php-config-printer/src/NodeFactory/ConstantNodeFactory.php
+++ b/packages/php-config-printer/src/NodeFactory/ConstantNodeFactory.php
@@ -28,6 +28,12 @@ final class ConstantNodeFactory
         $match = Strings::match($value, self::CLASS_CONST_FETCH_REGEX);
         if ($match !== null) {
             [$class, $constant] = explode('::', $value);
+
+            // Ignore static factories (FQCN::method)
+            if (method_exists($class, $constant)) {
+                return null;
+            }
+
             if (! $checkExistence) {
                 return new ClassConstFetch(new FullyQualified($class), $constant);
             }
diff --git a/packages/php-config-printer/src/ServiceOptionConverter/NodeModifier/SingleFactoryReferenceNodeModifier.php b/packages/php-config-printer/src/ServiceOptionConverter/NodeModifier/SingleFactoryReferenceNodeModifier.php
index 5cc164fcb..a517b9193 100644
--- a/packages/php-config-printer/src/ServiceOptionConverter/NodeModifier/SingleFactoryReferenceNodeModifier.php
+++ b/packages/php-config-printer/src/ServiceOptionConverter/NodeModifier/SingleFactoryReferenceNodeModifier.php
@@ -17,10 +17,10 @@ use Symplify\PhpConfigPrinter\ValueObject\FunctionName;
 final class SingleFactoryReferenceNodeModifier
 {
     /**
-     * @see https://regex101.com/r/Smydt1/1
+     * @see https://regex101.com/r/Smydt1/2
      * @var string
      */
-    private const SERVICE_METHOD_REGEX = '#(?<service_name>.*?)\:(?<method_name>\w+)#';
+    private const FACTORY_REGEX = '#(?<callee>.*?)(?<operator>\:{1,2})(?<method_name>\w+)#';
 
     public function __construct(
         private ArgsNodeFactory $argsNodeFactory
@@ -57,18 +57,17 @@ final class SingleFactoryReferenceNodeModifier
 
         $factoryValue = $singleArrayItem->value;
 
-        $match = Strings::match($factoryValue->value, self::SERVICE_METHOD_REGEX);
+        $match = Strings::match($factoryValue->value, self::FACTORY_REGEX);
         if ($match === null) {
             return;
         }
 
-        $serviceName = $match['service_name'];
-        $methodName = $match['method_name'];
+        $callee = $match['operator'] === ':'
+            ? $this->createServiceFuncCall($match['callee'])
+            : $this->argsNodeFactory->resolveExpr($match['callee']);
+        $methodNameString = new String_($match['method_name']);
 
-        $serviceFuncCall = $this->createServiceFuncCall($serviceName);
-        $methodNameString = new String_($methodName);
-
-        $singleArgValue->items = [new ArrayItem($serviceFuncCall), new ArrayItem($methodNameString)];
+        $singleArgValue->items = [new ArrayItem($callee), new ArrayItem($methodNameString)];
     }
 
     private function createServiceFuncCall(string $serviceName): FuncCall
diff --git a/phpstan.neon b/phpstan.neon
index 210fa9b78..b0777fdcd 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -166,6 +166,7 @@ parameters:
                 - packages/monorepo-builder/packages/Release/Process/ProcessRunner.php
                 # PHPUnit 9/10 compat
                 - packages/symfony-static-dumper/tests/FileSystem/AssetsCopierTest.php
+                - packages/php-config-printer/src/NodeFactory/ConstantNodeFactory.php
 
         -
             message: '#Function "property_exists\(\)" cannot be used/left in the code#'


Test patch:
diff --git a/packages/config-transformer/tests/Converter/ConfigFormatConverter/Source/StaticFactory.php b/packages/config-transformer/tests/Converter/ConfigFormatConverter/Source/StaticFactory.php
new file mode 100644
index 000000000..387c34565
--- /dev/null
+++ b/packages/config-transformer/tests/Converter/ConfigFormatConverter/Source/StaticFactory.php
@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Symplify\ConfigTransformer\Tests\Converter\ConfigFormatConverter\Source;
+
+/**
+ * @see https://symfony.com/doc/current/service_container/factories.html#static-factories
+ */
+final class StaticFactory
+{
+    public function create(): ExistingClass
+    {
+        return new ExistingClass();
+    }
+}
diff --git a/packages/config-transformer/tests/Converter/ConfigFormatConverter/YamlToPhp/Fixture/normal/factory/split_factory.yaml b/packages/config-transformer/tests/Converter/ConfigFormatConverter/YamlToPhp/Fixture/normal/factory/split_factory.yaml
index e7ff6613d..33159e047 100644
--- a/packages/config-transformer/tests/Converter/ConfigFormatConverter/YamlToPhp/Fixture/normal/factory/split_factory.yaml
+++ b/packages/config-transformer/tests/Converter/ConfigFormatConverter/YamlToPhp/Fixture/normal/factory/split_factory.yaml
@@ -1,12 +1,15 @@
 services:
     SomeClass:
         factory: "some_name:createMethod"
+    ExistingClass:
+        factory: 'Symplify\ConfigTransformer\Tests\Converter\ConfigFormatConverter\Source\StaticFactory::create'
 -----
 <?php
 
 declare(strict_types=1);
 
 use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+use Symplify\ConfigTransformer\Tests\Converter\ConfigFormatConverter\Source\StaticFactory;
 use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
 
 return static function (ContainerConfigurator $containerConfigurator): void {
@@ -14,4 +17,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
 
     $services->set('SomeClass')
         ->factory([service('some_name'), 'createMethod']);
+
+    $services->set('ExistingClass')
+        ->factory([StaticFactory::class, 'create']);
 };