Last active
January 3, 2016 01:19
-
-
Save spiechu/8388636 to your computer and use it in GitHub Desktop.
Check corresponding blog post at http://spiechu.pl/2014/01/12/using-php-traits-to-check-class-constants-values
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
interface CommonErrorCodes { | |
const ERR_STATUS_OK = 0; | |
const ERR_STATUS_NOT_FOUND = 1; | |
const ERR_STATUS_INVALID = 2; | |
const CHECK_ERR_PREFIX = 'ERR_'; | |
} | |
trait CheckClassConst { | |
/** | |
* Cache reflection class results. | |
* | |
* @var array [className => [constName => constValue], ...] | |
*/ | |
static private $classConstants = []; | |
/** | |
* Force class to implement valid constant names. | |
* | |
* Return '' to take all class constants or narrow names e.g. 'ERR'. | |
* | |
* @return string | |
*/ | |
abstract protected function getConstPrefix(); | |
/** | |
* Check $value against valid constant values. | |
* | |
* @param mixed $value | |
* @return boolean | |
*/ | |
public function isConstValueOK($value) { | |
// Current class is forced to implement this abstract method. | |
$constantPrefix = $this->getConstPrefix(); | |
$prefixRegex = '/^' . $constantPrefix . '/'; | |
foreach ($this->getClassConstants() as $constName => $constVal) { | |
// When values match, check if constant name also match. | |
if ($value === $constVal && (empty($constantPrefix) || preg_match($prefixRegex, $constName))) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Returns class constants. | |
* | |
* @return array [constName => constValue, ...] | |
*/ | |
protected function getClassConstants() { | |
$currentClass = get_class($this); | |
// Try to use static cache. | |
if (!array_key_exists($currentClass, self::$classConstants)) { | |
$reflection = new \ReflectionObject($this); | |
// This is where the magic works. | |
self::$classConstants[$currentClass] = $reflection->getConstants(); | |
} | |
return self::$classConstants[$currentClass]; | |
} | |
} | |
class BaseClass implements CommonErrorCodes { | |
const GARBAGE_STATUS = 8; | |
use CheckClassConst { | |
// Narrow isConstValueOK() visibility to protected. | |
// Don't let trait method leak to public API. | |
isConstValueOK as protected; | |
} | |
/** | |
* @var integer | |
*/ | |
protected $status; | |
/** | |
* Sets object status code. | |
* | |
* @param integer $status one of CommonErrorCodes::ERR_STATUS_* | |
* @throws \Exception when bad status code provided | |
*/ | |
public function setStatus($status) { | |
// This is where the magic works. | |
if (!$this->isConstValueOK($status)) { | |
throw new \Exception('Wrong status provided'); | |
} | |
$this->status = $status; | |
} | |
/** | |
* Implements trait abstract method. | |
* | |
* @return string | |
*/ | |
protected function getConstPrefix() { | |
return CommonErrorCodes::CHECK_ERR_PREFIX; | |
} | |
} | |
class Subclass extends BaseClass { | |
const GARBAGE_STATUS_EXTENDED = 9; | |
/** | |
* Change valid statuses to GARBAGEs. | |
* | |
* @return string | |
*/ | |
protected function getConstPrefix() { | |
return 'GARBAGE_'; | |
} | |
} | |
$base = new BaseClass(); | |
$base->setStatus(0); // true | |
$base->setStatus(8); // Exception thrown because of it's GARBAGE_STATUS value | |
$sub = new Subclass(); | |
$sub->setStatus(8); // true because of getConstPrefix()'s Subclass returns 'GARBAGE_' | |
$sub->setStatus(9); // true | |
$sub->setStatus(0); // Exception |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
IMHO this should be done with SplEnum (or similar) not traits.