Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 29 additions & 52 deletions src/Growthbook.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,11 @@ public function setAttributes(array $attributes): static
/**
* @param array<string,mixed> $attributes
* @return static
* @deprecated Use setAttributes() instead. withAttributes() will become immutable in 2.0.0.
*/
public function withAttributes(array $attributes): static
{
$self = clone $this;
$self->setAttributes($attributes);

return $self;
return $this->setAttributes($attributes);
}

/**
Expand All @@ -201,13 +199,11 @@ public function setSavedGroups(array $savedGroups): static
/**
* @param array<string,mixed> $savedGroups
* @return static
* @deprecated Use setSavedGroups() instead. withSavedGroups() will become immutable in 2.0.0.
*/
public function withSavedGroups(array $savedGroups): static
{
$self = clone $this;
$self->setSavedGroups($savedGroups);

return $self;
return $this->setSavedGroups($savedGroups);
}

/**
Expand All @@ -224,13 +220,11 @@ public function setTrackingCallback($trackingCallback): static
/**
* @param callable|null $trackingCallback
* @return static
* @deprecated Use setTrackingCallback() instead. withTrackingCallback() will become immutable in 2.0.0.
*/
public function withTrackingCallback($trackingCallback): static
{
$self = clone $this;
$self->setTrackingCallback($trackingCallback);

return $self;
return $this->setTrackingCallback($trackingCallback);
}

/**
Expand All @@ -255,13 +249,11 @@ public function setFeatures(array $features): static
/**
* @param array<string,Feature<mixed>|mixed> $features
* @return static
* @deprecated Use setFeatures() instead. withFeatures() will become immutable in 2.0.0.
*/
public function withFeatures(array $features): static
{
$self = clone $this;
$self->setFeatures($features);

return $self;
return $this->setFeatures($features);
}

/**
Expand All @@ -277,13 +269,11 @@ public function setForcedVariations(array $forcedVariations): static
/**
* @param array<string,int> $forcedVariations
* @return static
* @deprecated Use setForcedVariations() instead. withForcedVariations() will become immutable in 2.0.0.
*/
public function withForcedVariations(array $forcedVariations): static
{
$self = clone $this;
$self->setForcedVariations($forcedVariations);

return $self;
return $this->setForcedVariations($forcedVariations);
}

/**
Expand All @@ -299,13 +289,11 @@ public function setForcedFeatures(array $forcedFeatures): static
/**
* @param array<string, FeatureResult<mixed>> $forcedFeatures
* @return static
* @deprecated Use setForcedFeatures() instead. withForcedFeatures() will become immutable in 2.0.0.
*/
public function withForcedFeatures(array $forcedFeatures): static
{
$self = clone $this;
$self->setForcedFeatures($forcedFeatures);

return $self;
return $this->setForcedFeatures($forcedFeatures);
}

/**
Expand All @@ -321,13 +309,11 @@ public function setUrl(string $url): static
/**
* @param string $url
* @return static
* @deprecated Use setUrl() instead. withUrl() will become immutable in 2.0.0.
*/
public function withUrl(string $url): static
{
$self = clone $this;
$self->setUrl($url);

return $self;
return $this->setUrl($url);
}

/**
Expand All @@ -341,13 +327,12 @@ public function setLogger(?LoggerInterface $logger = null): void
/**
* @param LoggerInterface|null $logger
* @return static
* @deprecated Use setLogger() instead. withLogger() will become immutable in 2.0.0.
*/
public function withLogger(?LoggerInterface $logger = null): static
{
$self = clone $this;
$self->setLogger($logger);

return $self;
$this->setLogger($logger);
return $this;
}

/**
Expand All @@ -368,13 +353,11 @@ public function setHttpClient(\Psr\Http\Client\ClientInterface $client, \Psr\Htt
* @param \Psr\Http\Client\ClientInterface $client
* @param \Psr\Http\Message\RequestFactoryInterface $requestFactory
* @return static
* @deprecated Use setHttpClient() instead. withHttpClient() will become immutable in 2.0.0.
*/
public function withHttpClient(\Psr\Http\Client\ClientInterface $client, \Psr\Http\Message\RequestFactoryInterface $requestFactory): static
{
$self = clone $this;
$self->setHttpClient($client, $requestFactory);

return $self;
return $this->setHttpClient($client, $requestFactory);
}

/**
Expand All @@ -391,15 +374,14 @@ public function setApiTimeout(int $seconds): static

/**
* Set API request timeout in seconds
*
*
* @param int $seconds Timeout in seconds (default: 2)
* @return static
* @deprecated Use setApiTimeout() instead. withApiTimeout() will become immutable in 2.0.0.
*/
public function withApiTimeout(int $seconds): static
{
$self = clone $this;
$self->setApiTimeout($seconds);
return $self;
return $this->setApiTimeout($seconds);
}

/**
Expand All @@ -416,15 +398,14 @@ public function setApiConnectTimeout(int $seconds): static

/**
* Set API connection timeout in seconds
*
*
* @param int $seconds Connection timeout in seconds (default: 2)
* @return static
* @deprecated Use setApiConnectTimeout() instead. withApiConnectTimeout() will become immutable in 2.0.0.
*/
public function withApiConnectTimeout(int $seconds): static
{
$self = clone $this;
$self->setApiConnectTimeout($seconds);
return $self;
return $this->setApiConnectTimeout($seconds);
}

/**
Expand All @@ -446,13 +427,11 @@ public function setCache(\Psr\SimpleCache\CacheInterface $cache, ?int $ttl = nul
* @param \Psr\SimpleCache\CacheInterface $cache
* @param int|null $ttl
* @return static
* @deprecated Use setCache() instead. withCache() will become immutable in 2.0.0.
*/
public function withCache(\Psr\SimpleCache\CacheInterface $cache, ?int $ttl = null): static
{
$self = clone $this;
$self->setCache($cache, $ttl);

return $self;
return $this->setCache($cache, $ttl);
}

/**
Expand All @@ -472,13 +451,11 @@ public function setStickyBucketing(StickyBucketService $stickyBucketService, ?ar
* @param StickyBucketService $stickyBucketService
* @param array<string>|null $stickyBucketIdentifierAttributes
* @return static
* @deprecated Use setStickyBucketing() instead. withStickyBucketing() will become immutable in 2.0.0.
*/
public function withStickyBucketing(StickyBucketService $stickyBucketService, ?array $stickyBucketIdentifierAttributes): static
{
$self = clone $this;
$self->setStickyBucketing($stickyBucketService, $stickyBucketIdentifierAttributes);

return $self;
return $this->setStickyBucketing($stickyBucketService, $stickyBucketIdentifierAttributes);
}

/**
Expand Down
51 changes: 22 additions & 29 deletions tests/GrowthbookTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -992,41 +992,35 @@ public function testFluentInterface(): void
$this->assertSame(1, $actualFeatures['feature-1']->defaultValue);
$this->assertSame(2, $actualFeatures['feature-2']->defaultValue);

// Test that with* methods return a new instance (immutability)
// Test that with* methods return the same instance (mutable, backward compatible)
$newAttributes = ['id' => 999];
$newGb = $gb->withAttributes($newAttributes);

$this->assertNotSame($gb, $newGb); // Should be different instances
$this->assertSame($attributes, $gb->getAttributes()); // Original unchanged
$this->assertSame($newAttributes, $newGb->getAttributes()); // New has updated value
$this->assertSame($gb, $newGb); // Should be the same instance
$this->assertSame($newAttributes, $gb->getAttributes()); // Original updated in place
}

/**
* @return void
*/
public function testWithMethodsImmutability(): void
public function testWithMethodsMutability(): void
{
$originalGb = Growthbook::create()
$gb = Growthbook::create()
->withAttributes(['id' => 1])
->withFeatures(['test' => ['defaultValue' => true]])
->withUrl('/original');

// Test each with* method creates a new instance
$newGb1 = $originalGb->withAttributes(['id' => 2]);
$this->assertNotSame($originalGb, $newGb1);
$this->assertSame(['id' => 1], $originalGb->getAttributes());
$this->assertSame(['id' => 2], $newGb1->getAttributes());

$newGb2 = $originalGb->withFeatures(['new-feature' => ['defaultValue' => false]]);
$this->assertNotSame($originalGb, $newGb2);
$this->assertNotSame($newGb1, $newGb2);

$newGb3 = $originalGb->withUrl('/new-url');
$this->assertNotSame($originalGb, $newGb3);
$this->assertNotSame($newGb1, $newGb3);
$this->assertNotSame($newGb2, $newGb3);
$this->assertSame('/original', $originalGb->getUrl());
$this->assertSame('/new-url', $newGb3->getUrl());
// Test each with* method mutates the same instance (backward compatible behavior)
$result = $gb->withAttributes(['id' => 2]);
$this->assertSame($gb, $result);
$this->assertSame(['id' => 2], $gb->getAttributes());

$result2 = $gb->withFeatures(['new-feature' => ['defaultValue' => false]]);
$this->assertSame($gb, $result2);

$result3 = $gb->withUrl('/new-url');
$this->assertSame($gb, $result3);
$this->assertSame('/new-url', $gb->getUrl());
}

public function testDefaultTimeoutValues(): void
Expand Down Expand Up @@ -1086,19 +1080,18 @@ public function testSetApiConnectTimeoutEnforcesMinimum(): void
$this->assertEquals(1, $prop->getValue($gb));
}

public function testWithApiTimeoutReturnsNewInstance(): void
public function testWithApiTimeoutMutatesInstance(): void
{
$gb1 = new Growthbook();
$gb2 = $gb1->withApiTimeout(10);
$gb = new Growthbook();
$result = $gb->withApiTimeout(10);

$this->assertNotSame($gb1, $gb2);
$this->assertSame($gb, $result);

$ref = new \ReflectionClass($gb2);
$ref = new \ReflectionClass($gb);
$timeout = $ref->getProperty('apiTimeout');
$timeout->setAccessible(true);

$this->assertEquals(2, $timeout->getValue($gb1));
$this->assertEquals(10, $timeout->getValue($gb2));
$this->assertEquals(10, $timeout->getValue($gb));
}

public function testCustomHttpClientIsNotOverridden(): void
Expand Down