Skip to content

Instantly share code, notes, and snippets.

@colindecarlo
Last active September 3, 2018 14:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save colindecarlo/32af68ac16113848d068114ea0039131 to your computer and use it in GitHub Desktop.
Save colindecarlo/32af68ac16113848d068114ea0039131 to your computer and use it in GitHub Desktop.
<?php
namespace Vehikl\Feature\Tests;
use PHPUnit\Framework\TestCase;
class FeatureToggleTest extends TestCase
{
public function test_it_executes_the_on_method_when_the_feature_is_on()
{
global $enabled;
$enabled = true;
$this->assertTrue((new SomeClass())->go());
}
public function test_it_executes_the_off_method_when_the_feature_is_off()
{
global $enabled;
$enabled = false;
$this->assertFalse((new SomeClass())->go());
}
public function test_it_can_figure_out_which_feature_to_use_dynamically()
{
global $enabled;
$enabled = true;
$this->assertTrue((new SomeClass)->goAnotherWay());
}
public function test_it_can_figure_out_which_feature_to_use_dynamically_when_there_are_multiple_traits()
{
global $enabled;
$enabled = false;
$this->assertFalse((new SomeClass)->yetAnotherWayToGo());
}
}
trait WillThisWork
{
public function features() {
return [
'foobar' => ['on' => 'foo', 'off' => 'bar']
];
}
public function enabled()
{
global $enabled;
return $enabled;
}
protected function foo()
{
return $this->returnTrue();
}
protected function bar()
{
return $this->returnFalse();
}
}
trait AnotherFeature
{
public function features() {
return [
'quxqiz' => ['on' => 'qux', 'off' => 'qiz']
];
}
public function enabled()
{
global $enabled;
return $enabled;
}
public function qux()
{
return true;
}
public function qiz()
{
return false;
}
}
class SomeClass
{
use WillThisWork, AnotherFeature {
WillThisWork::features insteadof AnotherFeature;
WillThisWork::enabled insteadof AnotherFeature;
}
public function go()
{
$fm = new FeatureManager;
return $fm->feature(WillThisWork::class)->foobar();
}
public function goAnotherWay()
{
return FeatureManager::foobar();
}
public function yetAnotherWayToGo()
{
return FeatureManager::quxqiz();
}
protected function returnTrue()
{
return true;
}
protected function returnFalse()
{
return false;
}
}
class FeatureManager
{
public function feature($feature)
{
return $this->getFeature($feature);
}
protected function getFeature($feature)
{
$def = <<<DEF
return new class() extends \Vehikl\Feature\Tests\Feature {
use $feature;
};
DEF;
return eval($def);
}
public function __call($name, $arguments)
{
$caller = $this->getCaller();
$traits = (new \ReflectionClass($caller))->getTraits();
foreach($traits as $trait) {
if (! $trait->hasMethod('features')) {
continue;
}
$feature = $this->getFeature($trait->getName());
$features = $feature->features();
if (! (is_array($features) && array_key_exists($name, $features))) {
continue;
}
return $feature->__call($name, $arguments);
}
return false;
}
public static function __callStatic($name, $arguments)
{
return (new static)->__call($name, $arguments);
}
private function getCaller()
{
foreach(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 5) as $trace) {
if (in_array(get_class($trace['object'] ?? $this), [Feature::class, FeatureManager::class, get_class($this)])) {
continue;
}
return $trace['object'];
}
}
}
abstract class Feature
{
public function __call($method, $arguments)
{
$caller = $this->getCaller();
$features = $this->features();
$methodToCall = $this->enabled() ? $features[$method]['on'] : $features[$method]['off'];
return \Closure::bind(function () use ($methodToCall) {
return $this->$methodToCall();
}, $caller, get_class($caller))();
}
private function getCaller()
{
foreach(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 5) as $trace) {
if (in_array(get_class($trace['object'] ?? $this), [Feature::class, FeatureManager::class, get_class($this)])) {
continue;
}
return $trace['object'];
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment