99use Flagsmith \Engine \Utils \Exceptions \FeatureStateNotFound ;
1010use Flagsmith \Engine \Utils \Hashing ;
1111use Flagsmith \Engine \Utils \Semver ;
12+ use Flagsmith \Engine \Utils \StringValue ;
1213use Flagsmith \Engine \Utils \Types \Context \EvaluationContext ;
1314use Flagsmith \Engine \Utils \Types \Context \FeatureContext ;
1415use Flagsmith \Engine \Utils \Types \Context \SegmentRuleType ;
@@ -31,7 +32,7 @@ class Engine
3132 * Get the evaluation result for a given context.
3233 *
3334 * @param EvaluationContext $context The evaluation context.
34- * @return EvaluationResult EvaluationResult containing the context, flags, and segments
35+ * @return EvaluationResult EvaluationResult containing the evaluated flags and matched segments.
3536 */
3637 public static function getEvaluationResult ($ context ): EvaluationResult
3738 {
@@ -55,12 +56,9 @@ public static function getEvaluationResult($context): EvaluationResult
5556 $ segmentResult = new SegmentResult ();
5657 $ segmentResult ->key = $ segment ->key ;
5758 $ segmentResult ->name = $ segment ->name ;
59+ $ segmentResult ->metadata = $ segment ->metadata ?? null ;
5860 $ evaluatedSegments [] = $ segmentResult ;
5961
60- if (empty ($ segment ->overrides )) {
61- continue ;
62- }
63-
6462 foreach ($ segment ->overrides as $ overrideFeature ) {
6563 $ featureKey = $ overrideFeature ->feature_key ;
6664 $ evaluatedFeature = $ evaluatedFeatures [$ featureKey ] ?? null ;
@@ -252,16 +250,27 @@ private static function _contextMatchesCondition(
252250
253251 switch ($ condition ->operator ) {
254252 case SegmentConditionOperator::IN :
255- /** @var array<mixed> $inValues */
253+ if ($ contextValue === null ) {
254+ return false ;
255+ }
256256 if (is_array ($ condition ->value )) {
257257 $ inValues = $ condition ->value ;
258258 } else {
259- $ inValues = json_decode ($ condition ->value , true );
260- $ jsonDecodingFailed = $ inValues === null ;
261- if ($ jsonDecodingFailed || !is_array ($ inValues )) {
259+ try {
260+ $ inValues = json_decode (
261+ $ condition ->value ,
262+ associative: false , // Possibly catch objects
263+ flags: \JSON_THROW_ON_ERROR ,
264+ );
265+ if (!is_array ($ inValues )) {
266+ throw new \ValueError ('Invalid JSON array ' );
267+ }
268+ } catch (\JsonException | \ValueError ) {
262269 $ inValues = explode (', ' , $ condition ->value );
263270 }
264271 }
272+ $ inValues = array_map (fn ($ value ) => StringValue::from ($ value ), $ inValues );
273+ $ contextValue = StringValue::from ($ contextValue );
265274 return in_array ($ contextValue , $ inValues , strict: true );
266275
267276 case SegmentConditionOperator::PERCENTAGE_SPLIT :
@@ -282,19 +291,24 @@ private static function _contextMatchesCondition(
282291 $ threshold = $ hashing ->getHashedPercentageForObjectIds (
283292 $ objectIds ,
284293 );
285- return $ threshold <= floatval ( $ condition ->value );
294+ return $ threshold <= (( float ) $ condition ->value );
286295
287296 case SegmentConditionOperator::MODULO :
288297 if (!is_numeric ($ contextValue )) {
289298 return false ;
290299 }
291300
292- [$ divisor , $ remainder ] = explode ('| ' , $ condition ->value );
301+ $ parts = explode ('| ' , (string ) $ condition ->value );
302+ if (count ($ parts ) !== 2 ) {
303+ return false ;
304+ }
305+
306+ [$ divisor , $ remainder ] = $ parts ;
293307 if (!is_numeric ($ divisor ) || !is_numeric ($ remainder )) {
294308 return false ;
295309 }
296310
297- return floatval ($ contextValue) % $ divisor === $ remainder ;
311+ return fmod ($ contextValue, $ divisor) === (( float ) $ remainder) ;
298312
299313 case SegmentConditionOperator::IS_NOT_SET :
300314 return $ contextValue === null ;
@@ -309,9 +323,7 @@ private static function _contextMatchesCondition(
309323 return !str_contains ($ contextValue , $ condition ->value );
310324
311325 case SegmentConditionOperator::REGEX :
312- return boolval (
313- preg_match ("/ {$ condition ->value }/ " , $ contextValue ),
314- );
326+ return (bool ) preg_match ("/ {$ condition ->value }/ " , (string ) $ contextValue );
315327 }
316328
317329 if ($ contextValue === null ) {
@@ -328,10 +340,13 @@ private static function _contextMatchesCondition(
328340 default => null ,
329341 };
330342
331- if (is_string ($ contextValue ) && Semver::isSemver ($ contextValue )) {
332- $ contextValue = Semver::removeSemverSuffix ($ contextValue );
333- return $ operator !== null &&
334- version_compare ($ contextValue , $ condition ->value , $ operator );
343+ if ($ operator === null ) {
344+ return false ;
345+ }
346+
347+ if (Semver::isSemver ($ condition ->value ) && is_string ($ contextValue )) {
348+ $ actualVersion = Semver::removeSemverSuffix ($ condition ->value );
349+ return version_compare ($ contextValue , $ actualVersion , $ operator );
335350 }
336351
337352 return match ($ operator ) {
@@ -341,38 +356,37 @@ private static function _contextMatchesCondition(
341356 '>= ' => $ contextValue >= $ condition ->value ,
342357 '< ' => $ contextValue < $ condition ->value ,
343358 '<= ' => $ contextValue <= $ condition ->value ,
344- default => false ,
345359 };
346360 }
347361
348362 /**
363+ * Return a trait value by name, or a context value by JSONPath, or null
349364 * @param EvaluationContext $context
350365 * @param string $property
351- * @return mixed|array<mixed>|null
366+ * @return ? mixed
352367 */
353368 private static function _getContextValue ($ context , $ property )
354369 {
370+ if ($ context ->identity !== null ) {
371+ $ hasTrait = array_key_exists ($ property , $ context ->identity ->traits );
372+ if ($ hasTrait ) {
373+ return $ context ->identity ->traits [$ property ];
374+ }
375+ }
376+
355377 if (str_starts_with ($ property , '$. ' )) {
356378 try {
357- $ json = new JSONPath ($ context );
358- $ results = $ json ->find ($ property )->getData ();
379+ $ jsonpath = new JSONPath ($ context );
380+ $ results = $ jsonpath ->find ($ property )->getData ();
359381 } catch (JSONPathException ) {
360- // The unlikely case when a trait starts with "$." but isn't JSONPath
361- $ escapedProperty = addslashes ($ property );
362- $ path = "$.identity.traits[' {$ escapedProperty }'] " ;
363- $ json = new JSONPath ($ context );
364- $ results = $ json ->find ($ path )->getData ();
382+ return null ;
365383 }
366384
367- return match (count ($ results )) {
368- 0 => null ,
369- 1 => $ results [0 ],
370- default => $ results ,
371- };
372- }
385+ if (empty ($ results )) {
386+ return null ;
387+ }
373388
374- if ($ context ->identity !== null ) {
375- return $ context ->identity ->traits [$ property ] ?? null ;
389+ return $ results [0 ];
376390 }
377391
378392 return null ;
0 commit comments