1010use LaunchDarkly \Impl \Model \Rule ;
1111use LaunchDarkly \Impl \Model \Segment ;
1212use LaunchDarkly \Impl \Model \SegmentRule ;
13+ use LaunchDarkly \Impl \Util ;
1314use LaunchDarkly \LDContext ;
15+ use Psr \Log \LoggerInterface ;
1416
1517/**
1618 * Encapsulates the feature flag evaluation logic. The Evaluator has no direct access to the
2325class Evaluator
2426{
2527 private FeatureRequester $ _featureRequester ;
28+ private LoggerInterface $ _logger ;
2629
27- public function __construct (FeatureRequester $ featureRequester )
30+ public function __construct (FeatureRequester $ featureRequester, ? LoggerInterface $ logger = null )
2831 {
2932 $ this ->_featureRequester = $ featureRequester ;
33+ $ this ->_logger = $ logger ?: Util::makeNullLogger ();
3034 }
3135
3236 /**
@@ -54,40 +58,52 @@ private function evaluateInternal(
5458 LDContext $ context ,
5559 ?callable $ prereqEvalSink
5660 ): EvalResult {
57- if (!$ flag ->isOn ()) {
58- return EvaluatorHelpers::getOffResult ($ flag , EvaluationReason::off ());
59- }
61+ try {
62+ // The reason there's an extra try block here is that if something fails during evaluation of the
63+ // prerequisites, we don't want that to short-circuit evaluation of the base flag in the way that
64+ // an error normally would, where the whole evaluation would return an error result with a default
65+ // value. Instead we want the base flag to return its off variation, as it always does if any
66+ // prerequisites have failed.
67+ if (!$ flag ->isOn ()) {
68+ return EvaluatorHelpers::getOffResult ($ flag , EvaluationReason::off ());
69+ }
6070
61- $ prereqFailureReason = $ this ->checkPrerequisites ($ flag , $ context , $ prereqEvalSink );
62- if ($ prereqFailureReason !== null ) {
63- return EvaluatorHelpers::getOffResult ($ flag , $ prereqFailureReason );
64- }
71+ $ prereqFailureReason = $ this ->checkPrerequisites ($ flag , $ context , $ prereqEvalSink );
72+ if ($ prereqFailureReason !== null ) {
73+ return EvaluatorHelpers::getOffResult ($ flag , $ prereqFailureReason );
74+ }
6575
66- // Check to see if targets match
67- $ targetResult = $ this ->checkTargets ($ flag , $ context );
68- if ($ targetResult ) {
69- return $ targetResult ;
70- }
76+ // Check to see if targets match
77+ $ targetResult = $ this ->checkTargets ($ flag , $ context );
78+ if ($ targetResult ) {
79+ return $ targetResult ;
80+ }
7181
72- // Now walk through the rules and see if any match
73- foreach ($ flag ->getRules () as $ i => $ rule ) {
74- if ($ this ->ruleMatchesContext ($ rule , $ context )) {
75- return EvaluatorHelpers::getResultForVariationOrRollout (
76- $ flag ,
77- $ rule ,
78- $ rule ->isTrackEvents (),
79- $ context ,
80- EvaluationReason::ruleMatch ($ i , $ rule ->getId ())
81- );
82+ // Now walk through the rules and see if any match
83+ foreach ($ flag ->getRules () as $ i => $ rule ) {
84+ if ($ this ->ruleMatchesContext ($ rule , $ context )) {
85+ return EvaluatorHelpers::getResultForVariationOrRollout (
86+ $ flag ,
87+ $ rule ,
88+ $ rule ->isTrackEvents (),
89+ $ context ,
90+ EvaluationReason::ruleMatch ($ i , $ rule ->getId ())
91+ );
92+ }
8293 }
94+ return EvaluatorHelpers::getResultForVariationOrRollout (
95+ $ flag ,
96+ $ flag ->getFallthrough (),
97+ $ flag ->isTrackEventsFallthrough (),
98+ $ context ,
99+ EvaluationReason::fallthrough ()
100+ );
101+ } catch (EvaluationException $ e ) {
102+ return new EvalResult (new EvaluationDetail (null , null , EvaluationReason::error ($ e ->getErrorKind ())));
103+ } catch (\Throwable $ e ) {
104+ Util::logExceptionAtErrorLevel ($ this ->_logger , $ e , 'Unexpected error when evaluating flag ' . $ flag ->getKey ());
105+ return new EvalResult (new EvaluationDetail (null , null , EvaluationReason::error (EvaluationReason::EXCEPTION_ERROR )));
83106 }
84- return EvaluatorHelpers::getResultForVariationOrRollout (
85- $ flag ,
86- $ flag ->getFallthrough (),
87- $ flag ->isTrackEventsFallthrough (),
88- $ context ,
89- EvaluationReason::fallthrough ()
90- );
91107 }
92108
93109 private function checkPrerequisites (
@@ -99,9 +115,11 @@ private function checkPrerequisites(
99115 $ prereqOk = true ;
100116 try {
101117 $ prereqFeatureFlag = $ this ->_featureRequester ->getFeature ($ prereq ->getKey ());
102- if ($ prereqFeatureFlag == null ) {
118+ if ($ prereqFeatureFlag === null ) {
103119 $ prereqOk = false ;
104120 } else {
121+ // Note that if the prerequisite flag is off, we don't consider it a match no matter what its
122+ // off variation was. But we still need to evaluate it in order to generate an event.
105123 $ prereqEvalResult = $ this ->evaluateInternal ($ prereqFeatureFlag , $ context , $ prereqEvalSink );
106124 $ variation = $ prereq ->getVariation ();
107125 if (!$ prereqFeatureFlag ->isOn () || $ prereqEvalResult ->getDetail ()->getVariationIndex () !== $ variation ) {
@@ -111,7 +129,12 @@ private function checkPrerequisites(
111129 $ prereqEvalSink (new PrerequisiteEvaluationRecord ($ prereqFeatureFlag , $ flag , $ prereqEvalResult ));
112130 }
113131 }
114- } catch (\Exception $ e ) {
132+ } catch (\Throwable $ e ) {
133+ Util::logExceptionAtErrorLevel (
134+ $ this ->_logger ,
135+ $ e ,
136+ 'Unexpected error when evaluating prerequisite flag ' . $ prereq ->getKey ()
137+ );
115138 $ prereqOk = false ;
116139 }
117140 if (!$ prereqOk ) {
0 commit comments