diff --git a/.changeset/present-properties-match.md b/.changeset/present-properties-match.md new file mode 100644 index 0000000..2e34024 --- /dev/null +++ b/.changeset/present-properties-match.md @@ -0,0 +1,5 @@ +--- +"posthog-php": patch +--- + +Return false for the `is_not_set` operator during local evaluation when the property key is present. 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