Skip to content

Commit d9f4cc3

Browse files
authored
feat!: make with* methods immutable (2.0.0) (#60)
* feat!: make with* methods immutable (2.0.0) * restore GrowthbookTest
1 parent 68c4944 commit d9f4cc3

2 files changed

Lines changed: 81 additions & 49 deletions

File tree

src/Growthbook.php

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,13 @@ 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.
182181
*/
183182
public function withAttributes(array $attributes): static
184183
{
185-
return $this->setAttributes($attributes);
184+
$self = clone $this;
185+
$self->setAttributes($attributes);
186+
187+
return $self;
186188
}
187189

188190
/**
@@ -199,11 +201,13 @@ public function setSavedGroups(array $savedGroups): static
199201
/**
200202
* @param array<string,mixed> $savedGroups
201203
* @return static
202-
* @deprecated Use setSavedGroups() instead. withSavedGroups() will become immutable in 2.0.0.
203204
*/
204205
public function withSavedGroups(array $savedGroups): static
205206
{
206-
return $this->setSavedGroups($savedGroups);
207+
$self = clone $this;
208+
$self->setSavedGroups($savedGroups);
209+
210+
return $self;
207211
}
208212

209213
/**
@@ -220,11 +224,13 @@ public function setTrackingCallback($trackingCallback): static
220224
/**
221225
* @param callable|null $trackingCallback
222226
* @return static
223-
* @deprecated Use setTrackingCallback() instead. withTrackingCallback() will become immutable in 2.0.0.
224227
*/
225228
public function withTrackingCallback($trackingCallback): static
226229
{
227-
return $this->setTrackingCallback($trackingCallback);
230+
$self = clone $this;
231+
$self->setTrackingCallback($trackingCallback);
232+
233+
return $self;
228234
}
229235

230236
/**
@@ -249,11 +255,13 @@ public function setFeatures(array $features): static
249255
/**
250256
* @param array<string,Feature<mixed>|mixed> $features
251257
* @return static
252-
* @deprecated Use setFeatures() instead. withFeatures() will become immutable in 2.0.0.
253258
*/
254259
public function withFeatures(array $features): static
255260
{
256-
return $this->setFeatures($features);
261+
$self = clone $this;
262+
$self->setFeatures($features);
263+
264+
return $self;
257265
}
258266

259267
/**
@@ -269,11 +277,13 @@ public function setForcedVariations(array $forcedVariations): static
269277
/**
270278
* @param array<string,int> $forcedVariations
271279
* @return static
272-
* @deprecated Use setForcedVariations() instead. withForcedVariations() will become immutable in 2.0.0.
273280
*/
274281
public function withForcedVariations(array $forcedVariations): static
275282
{
276-
return $this->setForcedVariations($forcedVariations);
283+
$self = clone $this;
284+
$self->setForcedVariations($forcedVariations);
285+
286+
return $self;
277287
}
278288

279289
/**
@@ -289,11 +299,13 @@ public function setForcedFeatures(array $forcedFeatures): static
289299
/**
290300
* @param array<string, FeatureResult<mixed>> $forcedFeatures
291301
* @return static
292-
* @deprecated Use setForcedFeatures() instead. withForcedFeatures() will become immutable in 2.0.0.
293302
*/
294303
public function withForcedFeatures(array $forcedFeatures): static
295304
{
296-
return $this->setForcedFeatures($forcedFeatures);
305+
$self = clone $this;
306+
$self->setForcedFeatures($forcedFeatures);
307+
308+
return $self;
297309
}
298310

299311
/**
@@ -309,11 +321,13 @@ public function setUrl(string $url): static
309321
/**
310322
* @param string $url
311323
* @return static
312-
* @deprecated Use setUrl() instead. withUrl() will become immutable in 2.0.0.
313324
*/
314325
public function withUrl(string $url): static
315326
{
316-
return $this->setUrl($url);
327+
$self = clone $this;
328+
$self->setUrl($url);
329+
330+
return $self;
317331
}
318332

319333
/**
@@ -327,12 +341,13 @@ public function setLogger(?LoggerInterface $logger = null): void
327341
/**
328342
* @param LoggerInterface|null $logger
329343
* @return static
330-
* @deprecated Use setLogger() instead. withLogger() will become immutable in 2.0.0.
331344
*/
332345
public function withLogger(?LoggerInterface $logger = null): static
333346
{
334-
$this->setLogger($logger);
335-
return $this;
347+
$self = clone $this;
348+
$self->setLogger($logger);
349+
350+
return $self;
336351
}
337352

338353
/**
@@ -353,11 +368,13 @@ public function setHttpClient(\Psr\Http\Client\ClientInterface $client, \Psr\Htt
353368
* @param \Psr\Http\Client\ClientInterface $client
354369
* @param \Psr\Http\Message\RequestFactoryInterface $requestFactory
355370
* @return static
356-
* @deprecated Use setHttpClient() instead. withHttpClient() will become immutable in 2.0.0.
357371
*/
358372
public function withHttpClient(\Psr\Http\Client\ClientInterface $client, \Psr\Http\Message\RequestFactoryInterface $requestFactory): static
359373
{
360-
return $this->setHttpClient($client, $requestFactory);
374+
$self = clone $this;
375+
$self->setHttpClient($client, $requestFactory);
376+
377+
return $self;
361378
}
362379

363380
/**
@@ -377,11 +394,13 @@ public function setApiTimeout(int $seconds): static
377394
*
378395
* @param int $seconds Timeout in seconds (default: 2)
379396
* @return static
380-
* @deprecated Use setApiTimeout() instead. withApiTimeout() will become immutable in 2.0.0.
381397
*/
382398
public function withApiTimeout(int $seconds): static
383399
{
384-
return $this->setApiTimeout($seconds);
400+
$self = clone $this;
401+
$self->setApiTimeout($seconds);
402+
403+
return $self;
385404
}
386405

387406
/**
@@ -401,11 +420,13 @@ public function setApiConnectTimeout(int $seconds): static
401420
*
402421
* @param int $seconds Connection timeout in seconds (default: 2)
403422
* @return static
404-
* @deprecated Use setApiConnectTimeout() instead. withApiConnectTimeout() will become immutable in 2.0.0.
405423
*/
406424
public function withApiConnectTimeout(int $seconds): static
407425
{
408-
return $this->setApiConnectTimeout($seconds);
426+
$self = clone $this;
427+
$self->setApiConnectTimeout($seconds);
428+
429+
return $self;
409430
}
410431

411432
/**
@@ -427,11 +448,13 @@ public function setCache(\Psr\SimpleCache\CacheInterface $cache, ?int $ttl = nul
427448
* @param \Psr\SimpleCache\CacheInterface $cache
428449
* @param int|null $ttl
429450
* @return static
430-
* @deprecated Use setCache() instead. withCache() will become immutable in 2.0.0.
431451
*/
432452
public function withCache(\Psr\SimpleCache\CacheInterface $cache, ?int $ttl = null): static
433453
{
434-
return $this->setCache($cache, $ttl);
454+
$self = clone $this;
455+
$self->setCache($cache, $ttl);
456+
457+
return $self;
435458
}
436459

437460
/**
@@ -451,11 +474,13 @@ public function setStickyBucketing(StickyBucketService $stickyBucketService, ?ar
451474
* @param StickyBucketService $stickyBucketService
452475
* @param array<string>|null $stickyBucketIdentifierAttributes
453476
* @return static
454-
* @deprecated Use setStickyBucketing() instead. withStickyBucketing() will become immutable in 2.0.0.
455477
*/
456478
public function withStickyBucketing(StickyBucketService $stickyBucketService, ?array $stickyBucketIdentifierAttributes): static
457479
{
458-
return $this->setStickyBucketing($stickyBucketService, $stickyBucketIdentifierAttributes);
480+
$self = clone $this;
481+
$self->setStickyBucketing($stickyBucketService, $stickyBucketIdentifierAttributes);
482+
483+
return $self;
459484
}
460485

461486
/**

tests/GrowthbookTest.php

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -992,35 +992,41 @@ 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 the same instance (mutable, backward compatible)
995+
// Test that with* methods return a new instance (immutability)
996996
$newAttributes = ['id' => 999];
997997
$newGb = $gb->withAttributes($newAttributes);
998998

999-
$this->assertSame($gb, $newGb); // Should be the same instance
1000-
$this->assertSame($newAttributes, $gb->getAttributes()); // Original updated in place
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
10011002
}
10021003

10031004
/**
10041005
* @return void
10051006
*/
1006-
public function testWithMethodsMutability(): void
1007+
public function testWithMethodsImmutability(): void
10071008
{
1008-
$gb = Growthbook::create()
1009+
$originalGb = Growthbook::create()
10091010
->withAttributes(['id' => 1])
10101011
->withFeatures(['test' => ['defaultValue' => true]])
10111012
->withUrl('/original');
10121013

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());
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());
10241030
}
10251031

10261032
public function testDefaultTimeoutValues(): void
@@ -1080,18 +1086,19 @@ public function testSetApiConnectTimeoutEnforcesMinimum(): void
10801086
$this->assertEquals(1, $prop->getValue($gb));
10811087
}
10821088

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

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

1090-
$ref = new \ReflectionClass($gb);
1096+
$ref = new \ReflectionClass($gb2);
10911097
$timeout = $ref->getProperty('apiTimeout');
10921098
$timeout->setAccessible(true);
10931099

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

10971104
public function testCustomHttpClientIsNotOverridden(): void

0 commit comments

Comments
 (0)