Skip to content

Commit 68c4944

Browse files
authored
fix: restore mutable behavior of with* methods for backward compatibility (#59)
with* methods in v1.7.3 were changed to immutable (clone-based), which introduced a breaking change in a patch release, violating semver. This restores with* to mutable behavior (as in v1.7.2) and marks them as @deprecated in favor of the new set* equivalents. The with* methods will become properly immutable in v2.0.0.
1 parent da83425 commit 68c4944

2 files changed

Lines changed: 51 additions & 81 deletions

File tree

src/Growthbook.php

Lines changed: 29 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,11 @@ public function setAttributes(array $attributes): static
178178
/**
179179
* @param array<string,mixed> $attributes
180180
* @return static
181+
* @deprecated Use setAttributes() instead. withAttributes() will become immutable in 2.0.0.
181182
*/
182183
public function withAttributes(array $attributes): static
183184
{
184-
$self = clone $this;
185-
$self->setAttributes($attributes);
186-
187-
return $self;
185+
return $this->setAttributes($attributes);
188186
}
189187

190188
/**
@@ -201,13 +199,11 @@ public function setSavedGroups(array $savedGroups): static
201199
/**
202200
* @param array<string,mixed> $savedGroups
203201
* @return static
202+
* @deprecated Use setSavedGroups() instead. withSavedGroups() will become immutable in 2.0.0.
204203
*/
205204
public function withSavedGroups(array $savedGroups): static
206205
{
207-
$self = clone $this;
208-
$self->setSavedGroups($savedGroups);
209-
210-
return $self;
206+
return $this->setSavedGroups($savedGroups);
211207
}
212208

213209
/**
@@ -224,13 +220,11 @@ public function setTrackingCallback($trackingCallback): static
224220
/**
225221
* @param callable|null $trackingCallback
226222
* @return static
223+
* @deprecated Use setTrackingCallback() instead. withTrackingCallback() will become immutable in 2.0.0.
227224
*/
228225
public function withTrackingCallback($trackingCallback): static
229226
{
230-
$self = clone $this;
231-
$self->setTrackingCallback($trackingCallback);
232-
233-
return $self;
227+
return $this->setTrackingCallback($trackingCallback);
234228
}
235229

236230
/**
@@ -255,13 +249,11 @@ public function setFeatures(array $features): static
255249
/**
256250
* @param array<string,Feature<mixed>|mixed> $features
257251
* @return static
252+
* @deprecated Use setFeatures() instead. withFeatures() will become immutable in 2.0.0.
258253
*/
259254
public function withFeatures(array $features): static
260255
{
261-
$self = clone $this;
262-
$self->setFeatures($features);
263-
264-
return $self;
256+
return $this->setFeatures($features);
265257
}
266258

267259
/**
@@ -277,13 +269,11 @@ public function setForcedVariations(array $forcedVariations): static
277269
/**
278270
* @param array<string,int> $forcedVariations
279271
* @return static
272+
* @deprecated Use setForcedVariations() instead. withForcedVariations() will become immutable in 2.0.0.
280273
*/
281274
public function withForcedVariations(array $forcedVariations): static
282275
{
283-
$self = clone $this;
284-
$self->setForcedVariations($forcedVariations);
285-
286-
return $self;
276+
return $this->setForcedVariations($forcedVariations);
287277
}
288278

289279
/**
@@ -299,13 +289,11 @@ public function setForcedFeatures(array $forcedFeatures): static
299289
/**
300290
* @param array<string, FeatureResult<mixed>> $forcedFeatures
301291
* @return static
292+
* @deprecated Use setForcedFeatures() instead. withForcedFeatures() will become immutable in 2.0.0.
302293
*/
303294
public function withForcedFeatures(array $forcedFeatures): static
304295
{
305-
$self = clone $this;
306-
$self->setForcedFeatures($forcedFeatures);
307-
308-
return $self;
296+
return $this->setForcedFeatures($forcedFeatures);
309297
}
310298

311299
/**
@@ -321,13 +309,11 @@ public function setUrl(string $url): static
321309
/**
322310
* @param string $url
323311
* @return static
312+
* @deprecated Use setUrl() instead. withUrl() will become immutable in 2.0.0.
324313
*/
325314
public function withUrl(string $url): static
326315
{
327-
$self = clone $this;
328-
$self->setUrl($url);
329-
330-
return $self;
316+
return $this->setUrl($url);
331317
}
332318

333319
/**
@@ -341,13 +327,12 @@ public function setLogger(?LoggerInterface $logger = null): void
341327
/**
342328
* @param LoggerInterface|null $logger
343329
* @return static
330+
* @deprecated Use setLogger() instead. withLogger() will become immutable in 2.0.0.
344331
*/
345332
public function withLogger(?LoggerInterface $logger = null): static
346333
{
347-
$self = clone $this;
348-
$self->setLogger($logger);
349-
350-
return $self;
334+
$this->setLogger($logger);
335+
return $this;
351336
}
352337

353338
/**
@@ -368,13 +353,11 @@ public function setHttpClient(\Psr\Http\Client\ClientInterface $client, \Psr\Htt
368353
* @param \Psr\Http\Client\ClientInterface $client
369354
* @param \Psr\Http\Message\RequestFactoryInterface $requestFactory
370355
* @return static
356+
* @deprecated Use setHttpClient() instead. withHttpClient() will become immutable in 2.0.0.
371357
*/
372358
public function withHttpClient(\Psr\Http\Client\ClientInterface $client, \Psr\Http\Message\RequestFactoryInterface $requestFactory): static
373359
{
374-
$self = clone $this;
375-
$self->setHttpClient($client, $requestFactory);
376-
377-
return $self;
360+
return $this->setHttpClient($client, $requestFactory);
378361
}
379362

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

392375
/**
393376
* Set API request timeout in seconds
394-
*
377+
*
395378
* @param int $seconds Timeout in seconds (default: 2)
396379
* @return static
380+
* @deprecated Use setApiTimeout() instead. withApiTimeout() will become immutable in 2.0.0.
397381
*/
398382
public function withApiTimeout(int $seconds): static
399383
{
400-
$self = clone $this;
401-
$self->setApiTimeout($seconds);
402-
return $self;
384+
return $this->setApiTimeout($seconds);
403385
}
404386

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

417399
/**
418400
* Set API connection timeout in seconds
419-
*
401+
*
420402
* @param int $seconds Connection timeout in seconds (default: 2)
421403
* @return static
404+
* @deprecated Use setApiConnectTimeout() instead. withApiConnectTimeout() will become immutable in 2.0.0.
422405
*/
423406
public function withApiConnectTimeout(int $seconds): static
424407
{
425-
$self = clone $this;
426-
$self->setApiConnectTimeout($seconds);
427-
return $self;
408+
return $this->setApiConnectTimeout($seconds);
428409
}
429410

430411
/**
@@ -446,13 +427,11 @@ public function setCache(\Psr\SimpleCache\CacheInterface $cache, ?int $ttl = nul
446427
* @param \Psr\SimpleCache\CacheInterface $cache
447428
* @param int|null $ttl
448429
* @return static
430+
* @deprecated Use setCache() instead. withCache() will become immutable in 2.0.0.
449431
*/
450432
public function withCache(\Psr\SimpleCache\CacheInterface $cache, ?int $ttl = null): static
451433
{
452-
$self = clone $this;
453-
$self->setCache($cache, $ttl);
454-
455-
return $self;
434+
return $this->setCache($cache, $ttl);
456435
}
457436

458437
/**
@@ -472,13 +451,11 @@ public function setStickyBucketing(StickyBucketService $stickyBucketService, ?ar
472451
* @param StickyBucketService $stickyBucketService
473452
* @param array<string>|null $stickyBucketIdentifierAttributes
474453
* @return static
454+
* @deprecated Use setStickyBucketing() instead. withStickyBucketing() will become immutable in 2.0.0.
475455
*/
476456
public function withStickyBucketing(StickyBucketService $stickyBucketService, ?array $stickyBucketIdentifierAttributes): static
477457
{
478-
$self = clone $this;
479-
$self->setStickyBucketing($stickyBucketService, $stickyBucketIdentifierAttributes);
480-
481-
return $self;
458+
return $this->setStickyBucketing($stickyBucketService, $stickyBucketIdentifierAttributes);
482459
}
483460

484461
/**

tests/GrowthbookTest.php

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -992,41 +992,35 @@ public function testFluentInterface(): void
992992
$this->assertSame(1, $actualFeatures['feature-1']->defaultValue);
993993
$this->assertSame(2, $actualFeatures['feature-2']->defaultValue);
994994

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

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

10041003
/**
10051004
* @return void
10061005
*/
1007-
public function testWithMethodsImmutability(): void
1006+
public function testWithMethodsMutability(): void
10081007
{
1009-
$originalGb = Growthbook::create()
1008+
$gb = Growthbook::create()
10101009
->withAttributes(['id' => 1])
10111010
->withFeatures(['test' => ['defaultValue' => true]])
10121011
->withUrl('/original');
10131012

1014-
// Test each with* method creates a new instance
1015-
$newGb1 = $originalGb->withAttributes(['id' => 2]);
1016-
$this->assertNotSame($originalGb, $newGb1);
1017-
$this->assertSame(['id' => 1], $originalGb->getAttributes());
1018-
$this->assertSame(['id' => 2], $newGb1->getAttributes());
1019-
1020-
$newGb2 = $originalGb->withFeatures(['new-feature' => ['defaultValue' => false]]);
1021-
$this->assertNotSame($originalGb, $newGb2);
1022-
$this->assertNotSame($newGb1, $newGb2);
1023-
1024-
$newGb3 = $originalGb->withUrl('/new-url');
1025-
$this->assertNotSame($originalGb, $newGb3);
1026-
$this->assertNotSame($newGb1, $newGb3);
1027-
$this->assertNotSame($newGb2, $newGb3);
1028-
$this->assertSame('/original', $originalGb->getUrl());
1029-
$this->assertSame('/new-url', $newGb3->getUrl());
1013+
// Test each with* method mutates the same instance (backward compatible behavior)
1014+
$result = $gb->withAttributes(['id' => 2]);
1015+
$this->assertSame($gb, $result);
1016+
$this->assertSame(['id' => 2], $gb->getAttributes());
1017+
1018+
$result2 = $gb->withFeatures(['new-feature' => ['defaultValue' => false]]);
1019+
$this->assertSame($gb, $result2);
1020+
1021+
$result3 = $gb->withUrl('/new-url');
1022+
$this->assertSame($gb, $result3);
1023+
$this->assertSame('/new-url', $gb->getUrl());
10301024
}
10311025

10321026
public function testDefaultTimeoutValues(): void
@@ -1086,19 +1080,18 @@ public function testSetApiConnectTimeoutEnforcesMinimum(): void
10861080
$this->assertEquals(1, $prop->getValue($gb));
10871081
}
10881082

1089-
public function testWithApiTimeoutReturnsNewInstance(): void
1083+
public function testWithApiTimeoutMutatesInstance(): void
10901084
{
1091-
$gb1 = new Growthbook();
1092-
$gb2 = $gb1->withApiTimeout(10);
1085+
$gb = new Growthbook();
1086+
$result = $gb->withApiTimeout(10);
10931087

1094-
$this->assertNotSame($gb1, $gb2);
1088+
$this->assertSame($gb, $result);
10951089

1096-
$ref = new \ReflectionClass($gb2);
1090+
$ref = new \ReflectionClass($gb);
10971091
$timeout = $ref->getProperty('apiTimeout');
10981092
$timeout->setAccessible(true);
10991093

1100-
$this->assertEquals(2, $timeout->getValue($gb1));
1101-
$this->assertEquals(10, $timeout->getValue($gb2));
1094+
$this->assertEquals(10, $timeout->getValue($gb));
11021095
}
11031096

11041097
public function testCustomHttpClientIsNotOverridden(): void

0 commit comments

Comments
 (0)