now * self.name = parent.name * * Disclaimer: * * This is not production ready stuff. It's intended as an experiment * regarding OCL validation in PHP objects. * * @author Ivo Jansch * @license Creative Commons Attribution Share-alike * http://creativecommons.org/licenses/by-sa/3.0/ */ class OCLWrapper { /** * The object we are proxying. * * @var unknown_type */ private $obj; /** * Constructor */ public function __construct($obj) { $this->obj = $obj; } # PUBLIC PROXY MAGIC /** * Intercept method calls and pass them on to the actual object. * OCL constraints are validated before the call is made. If the * call violates OCL constraints, an Exception is thrown and the * methd is not called. * (To test the result of the actual call without actually changing * the state of an object when a constraint is violated, all OCL * checks are done against a clone of the object). */ public function __call($method, $args) { $cp = clone($this->obj); call_user_method_array($method, $cp, $args); $this->validateCall($cp, $method, $args); call_user_method_array($method, $this->obj, $args); } /** * Forward getters to the original class. */ public function __get($property) { return $this->obj->$property; } /** * Forward setters to the original class. * Before the value is actually set, it is set on a clone to * determine if any constraints would be violated. If so, an * exception is thrown. If now, the property is set. */ public function __set($property, $value) { $cp = clone($this->obj); // apply the property to the clone and validate if the clone is valid. $cp->$property = $value; $this->validateProperty($cp, $property, $value); $this->obj->$property = $value; } # INTERNALS /** * Validate OCL constraints of a certain property of an instance. */ private function validateProperty($object, $property=NULL, $value=NULL) { $constraints = $this->getPropertyConstraints($property, $value); foreach ($constraints as $constraint) { $this->validateConstraint($object, $constraint); } } /** * Validate OCL constraints of a method of an instance. */ private function validateCall($object, $method, $args) { $constraints = $this->getMethodConstraints($method); foreach ($constraints as $constraint) { $this->validateConstraint($object, $constraint); } } /** * Validate an OCL statement against an object instance. * * This uses an Evil Hardcoded String based pseudo OCL parser. * I would welcome if someone with OCL or compiler experience would * help getting a real OCL parser in here. */ private function validateConstraint($object, $constraint) { $ocl = $constraint; // extremely dirty pseudo ocl parse validaton code $constraint = str_replace("self.", "\$object->", $constraint); $constraint = str_replace("= ", " == \$object->", $constraint); $constraint = str_replace(".", "->", $constraint); $constraint = str_replace("now", "date(\"Y-m-d\")", $constraint); $constraint = "\$valid = ($constraint);"; eval($constraint); if (!$valid) { throw new Exception("Constraint '".$ocl."' violated"); } } /** * Retrieve docblocks at th classlevel. */ private function getClassComments() { $rc = new ReflectionClass(get_class($this->obj)); return $rc->getDocComment(); } /** * Retrieve constraints at the method level. Also retrieves * classlevel constraints. */ private function getMethodConstraints($method) { $comments = array(); // First collect docblocks $comment = $this->getClassComments(); if ($comment!="") $comments[] = $comment; $rp = new ReflectionMethod(get_class($this->obj), $method); $comment = $rp->getDocComment(); if ($comment) $comments[] = $comment; return $this->parseConstraints($comments); } /** * Retrieve constraints at the property level. Also retrieves * classlevel constraints. */ private function getPropertyConstraints($property, $value=NULL) { $comments = array(); // First collect docblocks $comment = $this->getClassComments(); if ($comment!="") $comments[] = $comment; $rp = new ReflectionProperty(get_class($this->obj), $property); $comment = $rp->getDocComment(); if ($comment) $comments[] = $comment; return $this->parseConstraints($comments); } /** * Parse a docblock into a (set of) constraint(s). */ private function parseConstraints($comment) { $result = array(); if (is_array($comment)) { foreach($comment as $cm) $result = array_merge($result, $this->parseConstraints($cm)); } else { // single string support $lines = explode("\n", $comment); foreach($lines as $line) { $found = preg_match("/@constraint (.*)/", $line, $matches); if ($found) $result[] = $matches[1]; } } return $result; } }