From 356a954a4ae5e0b392d9eef52f47e166ad11b7fe Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 26 Aug 2026 09:44:19 +0200 Subject: [PATCH 1/2] fix(flags): align presence operator semantics --- .changeset/present-properties-match.md | 5 ++ lib/FeatureFlag.php | 2 +- test/FeatureFlagLocalEvaluationTest.php | 67 ++++++++++++++++--------- 3 files changed, 50 insertions(+), 24 deletions(-) create mode 100644 .changeset/present-properties-match.md diff --git a/.changeset/present-properties-match.md b/.changeset/present-properties-match.md new file mode 100644 index 0000000..74f45ed --- /dev/null +++ b/.changeset/present-properties-match.md @@ -0,0 +1,5 @@ +--- +"posthog-php": patch +--- + +Align local `is_set` and `is_not_set` evaluation with partial property context. diff --git a/lib/FeatureFlag.php b/lib/FeatureFlag.php index 55f6e4c..3bccd05 100644 --- a/lib/FeatureFlag.php +++ b/lib/FeatureFlag.php @@ -32,7 +32,7 @@ public static function matchProperty($property, $propertyValues) } if ($operator == "is_not_set") { - throw new InconclusiveMatchException("can't match properties with operator is_not_set"); + return false; } $overrideValue = $propertyValues[$key]; diff --git a/test/FeatureFlagLocalEvaluationTest.php b/test/FeatureFlagLocalEvaluationTest.php index ee7064b..7809088 100644 --- a/test/FeatureFlagLocalEvaluationTest.php +++ b/test/FeatureFlagLocalEvaluationTest.php @@ -248,34 +248,55 @@ public function testMatchPropertyNotIn(): void ]); } - public function testMatchPropertyIsSet(): void + /** + * @dataProvider presentPresenceOperatorValuesProvider + */ + public function testMatchPropertyPresenceOperatorsTreatPresentValuesAsSet($value): void { - $prop = [ - "key" => "key", - "value" => "is_set", - "operator" => "is_set" - ]; - - self::assertTrue(FeatureFlag::matchProperty($prop, [ - "key" => "value", - ])); + foreach (["is_set" => true, "is_not_set" => false] as $operator => $expected) { + $prop = [ + "key" => "key", + "value" => $operator, + "operator" => $operator, + ]; - self::assertTrue(FeatureFlag::matchProperty($prop, [ - "key" => "value2", - ])); + self::assertSame($expected, FeatureFlag::matchProperty($prop, [ + "key" => $value, + ])); + } + } - self::assertTrue(FeatureFlag::matchProperty($prop, [ - "key" => "", - ])); + public static function presentPresenceOperatorValuesProvider(): array + { + return [ + 'null' => [null], + 'false' => [false], + 'zero' => [0], + 'empty string' => [''], + 'empty array' => [[]], + // stdClass is PHP's JSON-object host equivalent and keeps it distinct from [] (an empty array). + 'empty object' => [new \stdClass()], + ]; + } - self::assertTrue(FeatureFlag::matchProperty($prop, [ - "key" => null, - ])); + public function testMatchPropertyPresenceOperatorsAreInconclusiveForOmittedKey(): void + { + foreach (["is_set", "is_not_set"] as $operator) { + $prop = [ + "key" => "key", + "value" => $operator, + "operator" => $operator, + ]; - self::expectException(InconclusiveMatchException::class); - FeatureFlag::matchProperty($prop, [ - "key2" => "value", - ]); + try { + FeatureFlag::matchProperty($prop, [ + "key2" => "value", + ]); + self::fail("Expected InconclusiveMatchException for operator {$operator}"); + } catch (InconclusiveMatchException $exception) { + self::assertInstanceOf(InconclusiveMatchException::class, $exception); + } + } } public function testMatchPropertyContains(): void From 6ef03b9368dde8547039eacac5c5cf6860e0a49e Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 26 Aug 2026 16:48:53 +0200 Subject: [PATCH 2/2] docs: clarify is_not_set changeset --- .changeset/present-properties-match.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/present-properties-match.md b/.changeset/present-properties-match.md index 74f45ed..2e34024 100644 --- a/.changeset/present-properties-match.md +++ b/.changeset/present-properties-match.md @@ -2,4 +2,4 @@ "posthog-php": patch --- -Align local `is_set` and `is_not_set` evaluation with partial property context. +Return false for the `is_not_set` operator during local evaluation when the property key is present.