Repository: open-telemetry/opentelemetry-php (php)

Issue:
Implement factory for ResourceInfo
At the moment most of the methods in [ResourceInfo](https://github.com/open-telemetry/opentelemetry-php/blob/main/src/SDK/Resource/ResourceInfo.php) are factory methods. To better comply with [SRP](https://en.wikipedia.org/wiki/Single-responsibility_principle) those methods should be extracted into a factory class.



Pull request:
Created ResouceInfoFactory class
Created ResourceInfoFactory class and moved merge, defaultResource, and emptyResource methods from ResourceInfo to ResourceInfoFactory.

Fixes #621.

Patch:
diff --git a/examples/ParentSpanExample.php b/examples/ParentSpanExample.php
index 81dd9a0a..e4f7ff4e 100644
--- a/examples/ParentSpanExample.php
+++ b/examples/ParentSpanExample.php
@@ -7,14 +7,14 @@ use GuzzleHttp\Client;
 use GuzzleHttp\Psr7\HttpFactory;
 use OpenTelemetry\Contrib\Jaeger\Exporter as JaegerExporter;
 use OpenTelemetry\Contrib\Zipkin\Exporter as ZipkinExporter;
-use OpenTelemetry\SDK\Resource\ResourceInfo;
+use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
 use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
 use OpenTelemetry\SDK\Trace\Span;
 use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
 use OpenTelemetry\SDK\Trace\TracerProvider;
 
 $serviceName = 'ParentSpanExample';
-$resource = ResourceInfo::defaultResource();
+$resource = ResourceInfoFactory::defaultResource();
 $sampler = new AlwaysOnSampler();
 $tracerProvider = new TracerProvider($resource, $sampler);
 
diff --git a/examples/SpanResources.php b/examples/SpanResources.php
index b50e4e9e..e99ff578 100644
--- a/examples/SpanResources.php
+++ b/examples/SpanResources.php
@@ -5,6 +5,7 @@ require __DIR__ . '/../vendor/autoload.php';
 
 use OpenTelemetry\SDK\Common\Attribute\Attributes;
 use OpenTelemetry\SDK\Resource\ResourceInfo;
+use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
 use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;
 use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
 use OpenTelemetry\SDK\Trace\TracerProvider;
@@ -12,14 +13,14 @@ use OpenTelemetry\SemConv\ResourceAttributes;
 
 echo 'Starting ConsoleSpanExporter' . PHP_EOL;
 
-$resource = ResourceInfo::merge(ResourceInfo::create(new Attributes([
+$resource = ResourceInfoFactory::merge(ResourceInfo::create(new Attributes([
     //@see https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions
     ResourceAttributes::SERVICE_NAMESPACE => 'foo',
     ResourceAttributes::SERVICE_NAME => 'bar',
     ResourceAttributes::SERVICE_INSTANCE_ID => 1,
     ResourceAttributes::SERVICE_VERSION => '0.1',
     ResourceAttributes::DEPLOYMENT_ENVIRONMENT => 'development',
-])), ResourceInfo::defaultResource());
+])), ResourceInfoFactory::defaultResource());
 
 $tracerProvider =  new TracerProvider(
     new SimpleSpanProcessor(
diff --git a/src/SDK/Resource/Detectors/Composer.php b/src/SDK/Resource/Detectors/Composer.php
index 83e6253d..cec61acf 100644
--- a/src/SDK/Resource/Detectors/Composer.php
+++ b/src/SDK/Resource/Detectors/Composer.php
@@ -9,6 +9,7 @@ use Composer\InstalledVersions;
 use OpenTelemetry\SDK\Common\Attribute\Attributes;
 use OpenTelemetry\SDK\Resource\ResourceDetectorInterface;
 use OpenTelemetry\SDK\Resource\ResourceInfo;
+use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
 use OpenTelemetry\SemConv\ResourceAttributes;
 
 final class Composer implements ResourceDetectorInterface
@@ -16,7 +17,7 @@ final class Composer implements ResourceDetectorInterface
     public function getResource(): ResourceInfo
     {
         if (!class_exists(InstalledVersions::class)) {
-            return ResourceInfo::emptyResource();
+            return ResourceInfoFactory::emptyResource();
         }
 
         $attributes = [
diff --git a/src/SDK/Resource/Detectors/Composite.php b/src/SDK/Resource/Detectors/Composite.php
index 2e699a17..6c34dba9 100644
--- a/src/SDK/Resource/Detectors/Composite.php
+++ b/src/SDK/Resource/Detectors/Composite.php
@@ -6,6 +6,7 @@ namespace OpenTelemetry\SDK\Resource\Detectors;
 
 use OpenTelemetry\SDK\Resource\ResourceDetectorInterface;
 use OpenTelemetry\SDK\Resource\ResourceInfo;
+use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
 
 final class Composite implements ResourceDetectorInterface
 {
@@ -26,6 +27,6 @@ final class Composite implements ResourceDetectorInterface
             $resources[] = $resourceDetector->getResource();
         }
 
-        return ResourceInfo::merge(...$resources);
+        return ResourceInfoFactory::merge(...$resources);
     }
 }
diff --git a/src/SDK/Resource/ResourceInfo.php b/src/SDK/Resource/ResourceInfo.php
index cf84d775..b61960a0 100644
--- a/src/SDK/Resource/ResourceInfo.php
+++ b/src/SDK/Resource/ResourceInfo.php
@@ -4,12 +4,7 @@ declare(strict_types=1);
 
 namespace OpenTelemetry\SDK\Resource;
 
-use function in_array;
-use OpenTelemetry\SDK\Common\Attribute\Attributes;
 use OpenTelemetry\SDK\Common\Attribute\AttributesInterface;
-use OpenTelemetry\SDK\Common\Environment\Accessor;
-use OpenTelemetry\SDK\Common\Environment\KnownValues as Values;
-use OpenTelemetry\SDK\Common\Environment\Variables as Env;
 
 /**
  * A Resource is an immutable representation of the entity producing telemetry. For example, a process producing telemetry
@@ -34,85 +29,6 @@ class ResourceInfo
         return new ResourceInfo(clone $attributes, $schemaUrl);
     }
 
-    /**
-     * Merges resources into a new one.
-     *
-     * @param ResourceInfo ...$resources
-     * @return ResourceInfo
-     */
-    public static function merge(ResourceInfo ...$resources): self
-    {
-        $attributes = [];
-        $schemaUrl = null;
-
-        foreach ($resources as $resource) {
-            $attributes += $resource->getAttributes()->toArray();
-            $schemaUrl ??= $resource->getSchemaUrl();
-        }
-
-        return new ResourceInfo(new Attributes($attributes), $schemaUrl);
-    }
-
-    public static function defaultResource(): self
-    {
-        $detectors = Accessor::getList(Env::OTEL_PHP_DETECTORS);
-
-        if (in_array(Values::VALUE_ALL, $detectors)) {
-            return (new Detectors\Composite([
-                new Detectors\Environment(),
-                new Detectors\Host(),
-                new Detectors\OperatingSystem(),
-                new Detectors\Process(),
-                new Detectors\ProcessRuntime(),
-                new Detectors\Sdk(),
-                new Detectors\SdkProvided(),
-            ]))->getResource();
-        }
-
-        $resourceDetectors = [];
-
-        foreach ($detectors as $detector) {
-            switch ($detector) {
-                case Values::VALUE_DETECTORS_ENVIRONMENT:
-                    $resourceDetectors[] = new Detectors\Environment();
-
-                    break;
-                case Values::VALUE_DETECTORS_HOST:
-                    $resourceDetectors[] = new Detectors\Host();
-
-                    break;
-                case Values::VALUE_DETECTORS_OS:
-                    $resourceDetectors[] = new Detectors\OperatingSystem();
-
-                    break;
-                case Values::VALUE_DETECTORS_PROCESS:
-                    $resourceDetectors[] = new Detectors\Process();
-
-                    break;
-                case Values::VALUE_DETECTORS_PROCESS_RUNTIME:
-                    $resourceDetectors[] = new Detectors\ProcessRuntime();
-
-                    break;
-                case Values::VALUE_DETECTORS_SDK:
-                    $resourceDetectors[] = new Detectors\Sdk();
-
-                    break;
-                case Values::VALUE_DETECTORS_SDK_PROVIDED:
-                    $resourceDetectors[] = new Detectors\SdkProvided();
-
-                    break;
-                default:
-            }
-        }
-
-        return (new Detectors\Composite($resourceDetectors))->getResource();
-    }
-
-    public static function emptyResource(): self
-    {
-        return new ResourceInfo(new Attributes());
-    }
-
     public function getAttributes(): AttributesInterface
     {
         return $this->attributes;
diff --git a/src/SDK/Resource/ResourceInfoFactory.php b/src/SDK/Resource/ResourceInfoFactory.php
new file mode 100644
index 00000000..2e5e065d
--- /dev/null
+++ b/src/SDK/Resource/ResourceInfoFactory.php
@@ -0,0 +1,96 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\SDK\Resource;
+
+use function in_array;
+use OpenTelemetry\SDK\Common\Attribute\Attributes;
+use OpenTelemetry\SDK\Common\Environment\Accessor;
+use OpenTelemetry\SDK\Common\Environment\EnvironmentVariablesTrait;
+use OpenTelemetry\SDK\Common\Environment\KnownValues as Values;
+use OpenTelemetry\SDK\Common\Environment\Variables as Env;
+
+class ResourceInfoFactory
+{
+    use EnvironmentVariablesTrait;
+
+    /**
+     * Merges resources into a new one.
+     *
+     * @param ResourceInfo ...$resources
+     * @return ResourceInfo
+     */
+    public static function merge(ResourceInfo ...$resources): ResourceInfo
+    {
+        $attributes = [];
+        $schemaUrl = null;
+
+        foreach ($resources as $resource) {
+            $attributes += $resource->getAttributes()->toArray();
+            $schemaUrl ??= $resource->getSchemaUrl();
+        }
+
+        return ResourceInfo::create(new Attributes($attributes), $schemaUrl);
+    }
+
+    public static function defaultResource(): ResourceInfo
+    {
+        $detectors = Accessor::getList(Env::OTEL_PHP_DETECTORS);
+
+        if (in_array(Values::VALUE_ALL, $detectors)) {
+            return (new Detectors\Composite([
+                new Detectors\Environment(),
+                new Detectors\Host(),
+                new Detectors\OperatingSystem(),
+                new Detectors\Process(),
+                new Detectors\ProcessRuntime(),
+                new Detectors\Sdk(),
+                new Detectors\SdkProvided(),
+            ]))->getResource();
+        }
+
+        $resourceDetectors = [];
+
+        foreach ($detectors as $detector) {
+            switch ($detector) {
+                case Values::VALUE_DETECTORS_ENVIRONMENT:
+                    $resourceDetectors[] = new Detectors\Environment();
+
+                    break;
+                case Values::VALUE_DETECTORS_HOST:
+                    $resourceDetectors[] = new Detectors\Host();
+
+                    break;
+                case Values::VALUE_DETECTORS_OS:
+                    $resourceDetectors[] = new Detectors\OperatingSystem();
+
+                    break;
+                case Values::VALUE_DETECTORS_PROCESS:
+                    $resourceDetectors[] = new Detectors\Process();
+
+                    break;
+                case Values::VALUE_DETECTORS_PROCESS_RUNTIME:
+                    $resourceDetectors[] = new Detectors\ProcessRuntime();
+
+                    break;
+                case Values::VALUE_DETECTORS_SDK:
+                    $resourceDetectors[] = new Detectors\Sdk();
+
+                    break;
+                case Values::VALUE_DETECTORS_SDK_PROVIDED:
+                    $resourceDetectors[] = new Detectors\SdkProvided();
+
+                    break;
+                default:
+            }
+        }
+
+        return (new Detectors\Composite($resourceDetectors))->getResource();
+    }
+
+    public static function emptyResource(): ResourceInfo
+    {
+        return ResourceInfo::create(new Attributes());
+    }
+}
diff --git a/src/SDK/Trace/TracerProvider.php b/src/SDK/Trace/TracerProvider.php
index 4b1d2184..727577a8 100644
--- a/src/SDK/Trace/TracerProvider.php
+++ b/src/SDK/Trace/TracerProvider.php
@@ -9,6 +9,7 @@ use OpenTelemetry\API\Trace as API;
 use OpenTelemetry\API\Trace\NoopTracer;
 use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationLibrary;
 use OpenTelemetry\SDK\Resource\ResourceInfo;
+use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
 use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
 use OpenTelemetry\SDK\Trace\Sampler\ParentBased;
 use function register_shutdown_function;
@@ -38,7 +39,7 @@ final class TracerProvider implements API\TracerProviderInterface
         }
 
         $spanProcessors = is_array($spanProcessors) ? $spanProcessors : [$spanProcessors];
-        $resource = $resource ?? ResourceInfo::defaultResource();
+        $resource = $resource ?? ResourceInfoFactory::defaultResource();
         $sampler = $sampler ?? new ParentBased(new AlwaysOnSampler());
         $idGenerator = $idGenerator ?? new RandomIdGenerator();
         $spanLimits = $spanLimits ?? (new SpanLimitsBuilder())->build();


Test patch:
diff --git a/tests/Unit/SDK/Resource/ResourceTest.php b/tests/Unit/SDK/Resource/ResourceTest.php
index faa2ef41..c18a4138 100644
--- a/tests/Unit/SDK/Resource/ResourceTest.php
+++ b/tests/Unit/SDK/Resource/ResourceTest.php
@@ -10,6 +10,7 @@ use InvalidArgumentException;
 use OpenTelemetry\SDK\Common\Attribute\Attributes;
 use OpenTelemetry\SDK\Resource\Detectors;
 use OpenTelemetry\SDK\Resource\ResourceInfo;
+use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
 use OpenTelemetry\SemConv\ResourceAttributes;
 use PHPUnit\Framework\TestCase;
 
@@ -27,7 +28,7 @@ class ResourceTest extends TestCase
 
     public function test_empty_resource(): void
     {
-        $resource = ResourceInfo::emptyResource();
+        $resource = ResourceInfoFactory::emptyResource();
         $this->assertEmpty($resource->getAttributes());
     }
 
@@ -63,7 +64,7 @@ class ResourceTest extends TestCase
 
     public function test_all_default_resources(): void
     {
-        $resource = ResourceInfo::defaultResource();
+        $resource = ResourceInfoFactory::defaultResource();
 
         $this->assertSame(ResourceAttributes::SCHEMA_URL, $resource->getSchemaUrl());
 
@@ -93,7 +94,7 @@ class ResourceTest extends TestCase
     {
         $this->setEnvironmentVariable('OTEL_PHP_DETECTORS', 'none');
 
-        $resource = ResourceInfo::defaultResource();
+        $resource = ResourceInfoFactory::defaultResource();
 
         $this->assertNull($resource->getSchemaUrl());
 
@@ -120,7 +121,7 @@ class ResourceTest extends TestCase
         $this->setEnvironmentVariable('OTEL_PHP_DETECTORS', 'env');
         $this->setEnvironmentVariable('OTEL_SERVICE_NAME', 'test-service');
 
-        $resource = ResourceInfo::defaultResource();
+        $resource = ResourceInfoFactory::defaultResource();
 
         $this->assertSame(ResourceAttributes::SCHEMA_URL, $resource->getSchemaUrl());
 
@@ -147,7 +148,7 @@ class ResourceTest extends TestCase
     {
         $this->setEnvironmentVariable('OTEL_PHP_DETECTORS', 'os,host');
 
-        $resource = ResourceInfo::defaultResource();
+        $resource = ResourceInfoFactory::defaultResource();
 
         $this->assertSame(ResourceAttributes::SCHEMA_URL, $resource->getSchemaUrl());
 
@@ -173,7 +174,7 @@ class ResourceTest extends TestCase
     {
         $this->setEnvironmentVariable('OTEL_PHP_DETECTORS', 'process,process_runtime');
 
-        $resource = ResourceInfo::defaultResource();
+        $resource = ResourceInfoFactory::defaultResource();
 
         $this->assertSame(ResourceAttributes::SCHEMA_URL, $resource->getSchemaUrl());
 
@@ -199,7 +200,7 @@ class ResourceTest extends TestCase
     {
         $this->setEnvironmentVariable('OTEL_PHP_DETECTORS', 'sdk,sdk_provided');
 
-        $resource = ResourceInfo::defaultResource();
+        $resource = ResourceInfoFactory::defaultResource();
 
         $this->assertSame(ResourceAttributes::SCHEMA_URL, $resource->getSchemaUrl());
 
@@ -229,7 +230,7 @@ class ResourceTest extends TestCase
     {
         $primary = ResourceInfo::create(new Attributes(['name' => 'primary', 'empty' => '']));
         $secondary = ResourceInfo::create(new Attributes(['version' => '1.0.0', 'empty' => 'value']));
-        $result = ResourceInfo::merge($primary, $secondary);
+        $result = ResourceInfoFactory::merge($primary, $secondary);
 
         $name = $result->getAttributes()->get('name');
         $version = $result->getAttributes()->get('version');
@@ -289,35 +290,35 @@ class ResourceTest extends TestCase
 
     public function test_resource_service_name_default(): void
     {
-        $resource = ResourceInfo::defaultResource();
+        $resource = ResourceInfoFactory::defaultResource();
         $this->assertEquals('unknown_service', $resource->getAttributes()->get('service.name'));
     }
 
     public function test_resource_with_empty_environment_variable(): void
     {
         $this->setEnvironmentVariable('OTEL_RESOURCE_ATTRIBUTES', '');
-        $this->assertInstanceOf(ResourceInfo::class, ResourceInfo::defaultResource());
+        $this->assertInstanceOf(ResourceInfo::class, ResourceInfoFactory::defaultResource());
     }
 
     public function test_resource_with_invalid_environment_variable(): void
     {
         $this->setEnvironmentVariable('OTEL_RESOURCE_ATTRIBUTES', 'foo');
         $this->expectException(InvalidArgumentException::class);
-        $this->assertInstanceOf(ResourceInfo::class, ResourceInfo::defaultResource());
+        $this->assertInstanceOf(ResourceInfo::class, ResourceInfoFactory::defaultResource());
     }
 
     public function test_resource_from_environment_service_name_takes_precedence_over_resource_attribute(): void
     {
         $this->setEnvironmentVariable('OTEL_RESOURCE_ATTRIBUTES', 'service.name=bar');
         $this->setEnvironmentVariable('OTEL_SERVICE_NAME', 'foo');
-        $resource = ResourceInfo::defaultResource();
+        $resource = ResourceInfoFactory::defaultResource();
         $this->assertEquals('foo', $resource->getAttributes()->get('service.name'));
     }
 
     public function test_resource_from_environment_resource_attribute_takes_precedence_over_default(): void
     {
         $this->setEnvironmentVariable('OTEL_RESOURCE_ATTRIBUTES', 'service.name=foo');
-        $resource = ResourceInfo::defaultResource();
+        $resource = ResourceInfoFactory::defaultResource();
         $this->assertEquals('foo', $resource->getAttributes()->get('service.name'));
     }
 
diff --git a/tests/Unit/SDK/Trace/SpanTest.php b/tests/Unit/SDK/Trace/SpanTest.php
index 27d4b593..3d5e9a21 100644
--- a/tests/Unit/SDK/Trace/SpanTest.php
+++ b/tests/Unit/SDK/Trace/SpanTest.php
@@ -19,6 +19,7 @@ use OpenTelemetry\SDK\Common\Attribute\Attributes;
 use OpenTelemetry\SDK\Common\Attribute\AttributesInterface;
 use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationLibrary;
 use OpenTelemetry\SDK\Resource\ResourceInfo;
+use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
 use OpenTelemetry\SDK\Trace\Event;
 use OpenTelemetry\SDK\Trace\EventInterface;
 use OpenTelemetry\SDK\Trace\IdGeneratorInterface;
@@ -68,7 +69,7 @@ class SpanTest extends MockeryTestCase
     protected function setUp():void
     {
         $this->idGenerator = new RandomIdGenerator();
-        $this->resource = ResourceInfo::emptyResource();
+        $this->resource = ResourceInfoFactory::emptyResource();
         $this->instrumentationLibrary = new InstrumentationLibrary('test_library', '0.1.2');
 
         $this->spanProcessor = Mockery::spy(SpanProcessorInterface::class);
diff --git a/tests/Unit/SDK/Util/SpanData.php b/tests/Unit/SDK/Util/SpanData.php
index 5e93b99f..ea55ef9d 100644
--- a/tests/Unit/SDK/Util/SpanData.php
+++ b/tests/Unit/SDK/Util/SpanData.php
@@ -12,6 +12,7 @@ use OpenTelemetry\SDK\Common\Attribute\Attributes;
 use OpenTelemetry\SDK\Common\Attribute\AttributesInterface;
 use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationLibrary;
 use OpenTelemetry\SDK\Resource\ResourceInfo;
+use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
 use OpenTelemetry\SDK\Trace as SDK;
 use OpenTelemetry\SDK\Trace\EventInterface;
 use OpenTelemetry\SDK\Trace\LinkInterface;
@@ -48,7 +49,7 @@ class SpanData implements SDK\SpanDataInterface
         $this->attributes = new Attributes();
         $this->kind = API\SpanKind::KIND_INTERNAL;
         $this->status = StatusData::unset();
-        $this->resource = ResourceInfo::emptyResource();
+        $this->resource = ResourceInfoFactory::emptyResource();
         $this->instrumentationLibrary = InstrumentationLibrary::getEmpty(); /** @phan-suppress-current-line PhanAccessMethodInternal */
         $this->context = API\SpanContext::getInvalid();
         $this->parentContext = API\SpanContext::getInvalid();