├── Contract.php ├── Contract ├── Exception.php ├── Term.php └── Term │ ├── Abstract.php │ └── ProxyCollection.php ├── Examples ├── Array_Notation.php ├── Controller.php ├── Filter.php ├── Find.php ├── Image_Resize.php └── Model.php ├── LICENSE ├── Overview.txt └── README.md /Contract.php: -------------------------------------------------------------------------------- 1 | reflection['class'] = new ReflectionClass($object['class']); 28 | $this->reflection['method'] = $this->reflection['class']->getMethod($object['function']); 29 | $this->reflection['parameters'] = $this->reflection['method']->getParameters(); 30 | $this->name = $this->reflection['class']->name . '/' . $this->reflection['method']->name; 31 | 32 | foreach ($this->reflection['parameters'] as $p => $parameter){ 33 | 34 | $termName = $parameter->name; 35 | $termData = (isset($object['args'][$p])) ? $object['args'][$p] : (($parameter->isDefaultValueAvailable()) ? $parameter->getDefaultValue() : null); 36 | 37 | $this->terms[$termName] = new Contract_Term($termName, $termData, $this); 38 | 39 | } 40 | 41 | } 42 | 43 | if (!is_null($termsData)) $this->terms($termsData); 44 | if (!is_null($metAction)) switch ($metAction){ 45 | 46 | case self::MET_OR_THROW: $this->metOrThrow(); break; 47 | default: $this->metOrThrow(); break; 48 | 49 | } 50 | 51 | } 52 | 53 | public function data($term){ 54 | 55 | return $this->getData($term); 56 | 57 | } 58 | 59 | public function debug($print = true){ 60 | 61 | $debug = array(); 62 | 63 | $terms = $this->getTerms(); 64 | foreach ($terms as $term){ 65 | 66 | $termName = $term->getName(); 67 | $debug[$termName] = $term->debug(false); 68 | 69 | } 70 | 71 | if ($print) print_r($debug); 72 | 73 | return $debug; 74 | 75 | } 76 | 77 | public static function factory(array $termsData = null, $metAction = null){ 78 | 79 | $contract = new self($termsData, $metAction, Contract::FROM_FACTORY); 80 | return $contract; 81 | 82 | } 83 | 84 | public function find($termName){ 85 | 86 | $terms = explode('/', $termName); 87 | $current = $this; 88 | 89 | foreach ($terms as $term){ 90 | 91 | $term = $current->getTerm($term, false); 92 | if ($term instanceof Contract_Term_Abstract) $current = $term; 93 | else { 94 | 95 | throw new Contract_Exception('Could not find: ' . $termName); 96 | 97 | } 98 | 99 | } 100 | 101 | return $current; 102 | 103 | } 104 | 105 | public function getData($term){ 106 | 107 | return $this->getTerm($term)->data(); 108 | 109 | } 110 | 111 | public function getMets(){ 112 | 113 | $mets = array(); 114 | 115 | $terms = $this->getTerms(); 116 | foreach ($terms as $term){ 117 | 118 | $termMets = $term->getMets(); 119 | $mets = array_merge($mets, $termMets); 120 | 121 | } 122 | 123 | return $mets; 124 | 125 | } 126 | 127 | public function getName(){ 128 | 129 | return $this->name; 130 | 131 | } 132 | 133 | public function getTerm($name, $find = true, $data = null){ 134 | 135 | if ($find && is_null($data)) $term = $this->find($name); 136 | else $term = $this->terms[$name]; 137 | 138 | if (!$term instanceof Contract_Term_Abstract) $term = $this->terms[$name] = new Contract_Term($name, $data, $this); 139 | 140 | return $term; 141 | 142 | } 143 | 144 | public function getTerms(){ 145 | 146 | return $this->terms; 147 | 148 | } 149 | 150 | public function met(){ 151 | 152 | $mets = $this->getMets(); 153 | 154 | foreach ($mets as $met) if ($met['met'] !== true){ 155 | 156 | if ($met['predicate'] != 'Allowed') return false; 157 | 158 | } 159 | 160 | return true; 161 | 162 | } 163 | 164 | public function metOrThrow(){ 165 | 166 | $mets = $this->getMets(); 167 | 168 | foreach ($mets as $met) if ($met['met'] !== true){ 169 | 170 | if ($met['predicate'] != 'Allowed'){ 171 | 172 | throw new Contract_Exception('Contract term `' . $met['term'] . '` did not meet its requirement for ' . $met['predicate'] . '.', $met['name']); 173 | 174 | } 175 | 176 | } 177 | 178 | return true; 179 | 180 | } 181 | 182 | public function term($name, $data = null){ 183 | 184 | $term = $this->getTerm($name, true, $data); 185 | return $term; 186 | 187 | } 188 | 189 | public function terms(array $termsData){ 190 | 191 | foreach ($termsData as $termName => $termConfig){ 192 | 193 | $name = $termName; 194 | $data = null; 195 | $definition = $termConfig; 196 | 197 | if (is_array($termConfig)){ 198 | 199 | if (array_key_exists('data', $termConfig)) $data = $termConfig['data']; 200 | if (array_key_exists('definition', $termConfig)) $definition = $termConfig['definition']; 201 | 202 | } 203 | 204 | $term = $this->getTerm($name, true, $data); 205 | 206 | if (is_array($definition)){ 207 | 208 | foreach ($definition as $definitionName => $definitionValue){ 209 | 210 | if (method_exists($term, $definitionName)){ 211 | 212 | switch ($definitionName){ 213 | 214 | case 'element': 215 | 216 | if (is_array($definitionValue)){ 217 | 218 | foreach ($definitionValue as $elementName => $elementConfig){ 219 | 220 | $element = $term->element($elementName); 221 | 222 | if (is_array($elementConfig)){ 223 | 224 | foreach ($elementConfig as $elementDefinitionName => $elementDefinitionValue){ 225 | 226 | if (method_exists($element, $elementDefinitionName)){ 227 | 228 | if (!is_array($elementDefinitionValue) || in_array($elementDefinitionName, array('allowed', 'alone', 'in'))) $elementDefinitionValue = array($elementDefinitionValue); 229 | call_user_func_array(array($element, $elementDefinitionName), $elementDefinitionValue); 230 | 231 | 232 | } 233 | else if (method_exists($element, $elementDefinitionValue)){ 234 | 235 | call_user_func_array(array($element, $elementDefinitionValue), array()); 236 | 237 | } 238 | 239 | } 240 | 241 | } 242 | else { 243 | 244 | if (method_exists($element, $elementConfig)){ 245 | 246 | call_user_func_array(array($element, $elementConfig), array()); 247 | 248 | } 249 | 250 | } 251 | 252 | } 253 | 254 | } 255 | 256 | break; 257 | 258 | case 'elements': 259 | 260 | throw new Exception('Contract_Term::elements() is not supported when creating terms via array notation.'); 261 | 262 | break; 263 | 264 | default: 265 | 266 | if (!is_array($definitionValue) || in_array($definitionName, array('allowed', 'alone', 'in'))) $definitionValue = array($definitionValue); 267 | call_user_func_array(array($term, $definitionName), $definitionValue); 268 | 269 | break; 270 | 271 | } 272 | 273 | } 274 | else if (method_exists($term, $definitionValue)){ 275 | 276 | call_user_func_array(array($term, $definitionValue), array()); 277 | 278 | } 279 | 280 | } 281 | 282 | } 283 | else { 284 | 285 | if (method_exists($term, $definition)){ 286 | 287 | call_user_func_array(array($term, $definition), array()); 288 | 289 | } 290 | 291 | } 292 | 293 | } 294 | 295 | return $this; 296 | 297 | } 298 | 299 | public function __toString(){ 300 | 301 | $string = '[contract' . (!empty($this->name) ? ':' . $this->name : '') . "]\n"; 302 | 303 | $terms = $this->getTerms(); 304 | foreach ($terms as $term) $string .= $term; 305 | 306 | return $string; 307 | 308 | } 309 | 310 | } 311 | 312 | ?> -------------------------------------------------------------------------------- /Contract/Exception.php: -------------------------------------------------------------------------------- 1 | term = $term; 11 | 12 | } 13 | 14 | public function __get($name){ 15 | 16 | $property = null; 17 | 18 | if ($name == 'term') $property = $this->term; 19 | 20 | return $property; 21 | 22 | } 23 | 24 | } 25 | 26 | ?> 27 | -------------------------------------------------------------------------------- /Contract/Term.php: -------------------------------------------------------------------------------- 1 | setName($name); 67 | $this->setData($data); 68 | $this->setParent($parent); 69 | $this->setChildren(); 70 | 71 | } 72 | 73 | public function allowed($keys, $propagate = false){ if ($keys == '*'){ foreach ($this->children as $key => $term){ $this->dataAllowable[] = $key; if ($propagate) $term->allowed('*', $propagate); } } else { if (is_array($keys)) $this->dataAllowable = $keys; else $this->dataAllowable[] = $keys; } return $this; } 74 | public function alone(array $exceptions = null){ $this->meetAlone = (!is_null($exceptions)) ? $exceptions : true; return $this; } 75 | public function alpha(){ $this->meetAlpha = true; return $this; } 76 | public function alphaNumeric(){ $this->meetAlphaNumeric = true; return $this; } 77 | public function alphaDash(){ $this->meetAlphaDash = true; return $this; } 78 | public function alphaUnderscore(){ $this->meetAlphaUnderscore = true; return $this; } 79 | public function arraylist($all = false){ $this->meetArraylist = ($all) ? 'all' : 'one'; return $this; } 80 | public function base64(){ $this->meetBase64 = true; return $this; } 81 | public function between($value, $value2){ $this->meetBetween = array($value, $value2); return $this; } 82 | public function boolean($strict = true){ $this->meetBoolean = ($strict) ? 'strict' : 'loose'; return $this; } 83 | public function count($value = null, $value2 = null){ $this->meetCount = (!is_null($value) || !is_null($value2)) ? array($value, $value2) : false; return $this; } 84 | public function date(){ $this->meetDate = true; return $this; } 85 | public function datetime(){ $this->meetDatetime = true; return $this; } 86 | public function decimal(){ $this->meetDecimal = true; return $this; } 87 | public function earlier($timestamp = null){ $this->meetEarlier = (!is_null($timestamp)) ? strtotime($timestamp) : time(); return $this; } 88 | public function email(){ $this->meetEmail = true; return $this; } 89 | public function equals($value){ $this->meetEquals = $value; return $this; } 90 | public function file(){ $this->meetFile = true; return $this; } 91 | public function greaterThan($value){ $this->meetGreaterThan = $value; return $this; } 92 | public function id(){ $this->meetId = true; return $this; } 93 | public function in(array $values){ $this->meetIn = $values; return $this; } 94 | public function integer(){ $this->meetInteger = true; return $this; } 95 | public function ip(){ $this->meetIp = true; return $this; } 96 | public function later($timestamp = null){ $this->meetLater = (!is_null($timestamp)) ? strtotime($timestamp) : time(); return $this; } 97 | public function length($value = null, $value2 = null){ $this->meetLength = (!is_null($value) || !is_null($value2)) ? array($value, $value2) : false; return $this; } 98 | public function lessThan($value){ $this->meetLessThan = $value; return $this; } 99 | public function many(){ $this->meetMany = true; return $this; } 100 | public function natural(){ $this->meetNatural = true; return $this; } 101 | public function naturalPositive(){ $this->meetNaturalPositive = true; return $this; } 102 | public function none(){ $this->meetNone = true; return $this; } 103 | public function not($value){ $this->meetNot = $value; return $this; } 104 | public function null(){ $this->meetNull = true; return $this; } 105 | public function numeric(){ $this->meetNumeric = true; return $this; } 106 | public function object($type = null){ $this->meetObject = (!is_null($type)) ? $type : true; return $this; } 107 | public function one(){ $this->meetOne = true; return $this; } 108 | public function optional($optional = true){ $this->meetOptional = $optional; return $this; } 109 | public function phone($strict = false){ $this->meetPhone = ($strict) ? 'strict' : 'loose'; return $this; } 110 | public function required($required = true){ $this->meetRequired = $required; return $this; } 111 | public function row($all = false){ $this->meetRow = ($all) ? 'all' : 'one'; return $this; } 112 | public function string(){ $this->meetString = true; return $this; } 113 | public function time(){ $this->meetTime = true; return $this; } 114 | public function url(){ $this->meetUrl = true; return $this; } 115 | public function withData(){ $this->meetWithData = true; return $this; } 116 | 117 | public function data($type = null){ 118 | 119 | return $this->getData($type); 120 | 121 | } 122 | 123 | public function debug($print = true){ 124 | 125 | $mets = $this->getMets(); 126 | 127 | foreach ($mets as $m => $met) if ($met['met'] === true) unset($mets[$m]); 128 | 129 | if ($print) print_r($mets); 130 | 131 | return $mets; 132 | 133 | } 134 | 135 | public function element($name){ 136 | 137 | if (!in_array($name, $this->dataAllowable)) $this->dataAllowable[] = $name; 138 | if (!isset($this->children[$name])) $this->children[$name] = new Site_Contract_Term($name, '', $this); 139 | 140 | return $this->getTerm($name); 141 | 142 | } 143 | 144 | public function elements(){ 145 | 146 | require_once('Term/ProxyCollection.php'); 147 | return new Contract_Term_ProxyCollection($this); 148 | 149 | } 150 | 151 | public function end($recursion = false){ 152 | 153 | return $this->parent(); 154 | 155 | } 156 | 157 | public function find($termName){ 158 | 159 | $terms = explode('/', $termName); 160 | $current = $this; 161 | 162 | foreach ($terms as $term){ 163 | 164 | $term = $current->getTerm($term, false); 165 | if ($term instanceof Contract_Term_Abstract) $current = $term; 166 | else { 167 | 168 | require_once('Exception.php'); 169 | throw new Contract_Exception('Could not find: ' . $termName); 170 | 171 | } 172 | 173 | } 174 | 175 | return $current; 176 | 177 | } 178 | 179 | public function getData($type = null){ 180 | 181 | switch ($type){ 182 | case self::DATA_ALLOWABLE: return $this->dataAllowable; 183 | case self::DATA_MET: return $this->getMetData(false); 184 | case self::DATA_ORIGINAL: return $this->data; 185 | default: return $this->getMetData(); 186 | } 187 | 188 | } 189 | 190 | public function getFullName(){ 191 | 192 | if (is_null($this->fullName)){ 193 | 194 | $fullName = $this->getName(); 195 | if ($this->parent && $this->parent instanceof Contract_Term) $fullName = $this->parent->getFullName() . '/' . $fullName; 196 | $this->fullName = $fullName; 197 | 198 | } 199 | 200 | return $this->fullName; 201 | 202 | } 203 | 204 | public function getMetData($rescan = false){ 205 | 206 | if ($this->scanned && !$rescan) return $this->dataMet; 207 | 208 | $dataMet = $this->getData(self::DATA_ORIGINAL); 209 | $mets = $this->getMets(); 210 | 211 | foreach ($dataMet as $key => $value){ 212 | 213 | $keyFullName = $this->element($key)->getFullName(); 214 | 215 | foreach ($mets as $metFullName => $met){ 216 | 217 | $metFullName = implode('/', array_slice(explode('/', $metFullName), 0, -1)); 218 | if (strpos($metFullName, $keyFullName) === 0){ 219 | 220 | if ($met['met'] === false){ 221 | 222 | if ($met['predicate'] == 'Allowed') eval('unset($dataMet[' . implode('][', array_slice(explode('/', $metFullName), 1)) . ']);'); 223 | else unset($dataMet[$key]); 224 | 225 | } 226 | 227 | } 228 | 229 | } 230 | 231 | } 232 | 233 | $this->dataMet = $dataMet; 234 | $this->scanned = true; 235 | 236 | return $this->dataMet; 237 | 238 | } 239 | 240 | public function getMets(){ 241 | 242 | $mets = array(); 243 | 244 | if ($this->meetOptional == true && empty($this->data)) return $mets; 245 | 246 | $properties = get_object_vars($this); 247 | foreach ($properties as $propertyName => $propertyValue) if (substr($propertyName, 0, 4) == 'meet' && $propertyValue !== false){ 248 | 249 | $condition = substr($propertyName, 4); 250 | $method = 'met' . $condition; 251 | $methodMets = $this->$method(); 252 | $mets = array_merge($mets, $methodMets); 253 | 254 | } 255 | 256 | foreach ($this->children as $childName => $childTerm){ 257 | 258 | $childMets = $childTerm->getMets(); 259 | $mets = array_merge($mets, $childMets); 260 | 261 | } 262 | 263 | return $mets; 264 | 265 | } 266 | 267 | public function getName(){ 268 | 269 | return $this->name; 270 | 271 | } 272 | 273 | protected function getParent($recursion = false){ 274 | 275 | if ($recursion && $this->parent instanceof Contract_Term){ 276 | 277 | if (is_string($recursion) && $recursion == $this->parent->getName()) return $this->parent; 278 | if (is_int($recursion)) $recursion--; 279 | $parent = $this->parent->getParent($recursion); 280 | 281 | } 282 | else $parent = $this->parent; 283 | 284 | return $parent; 285 | 286 | } 287 | 288 | protected function getPredicateFullName($predicateName){ 289 | 290 | $fullName = $this->getFullName(); 291 | $predicateFullName = $fullName . '/' . $predicateName; 292 | return $predicateFullName; 293 | 294 | } 295 | 296 | public function getTerm($name, $find = true){ 297 | 298 | if ($find) $term = $this->find($name); 299 | else $term = $this->children[$name]; 300 | return $term; 301 | 302 | } 303 | 304 | public function met(){ 305 | 306 | $mets = $this->getMets(); 307 | 308 | foreach ($mets as $met) if ($met['met'] !== true){ 309 | 310 | if ($met['predicate'] != 'Allowed') return false; 311 | 312 | } 313 | 314 | return true; 315 | 316 | } 317 | 318 | public function metOrThrow(){ 319 | 320 | $mets = $this->getMets(); 321 | 322 | foreach ($mets as $met) if ($met['met'] !== true){ 323 | 324 | if ($met['predicate'] != 'Allowed'){ 325 | 326 | require_once('Exception.php'); 327 | throw new Contract_Exception('Contract term `' . $met['term'] . '` did not meet its requirement for ' . $met['predicate'] . '.', $met['name']); 328 | 329 | } 330 | 331 | } 332 | 333 | return true; 334 | 335 | } 336 | 337 | protected function metAllowed(){ return $this->scanOne('Allowed'); } 338 | protected function metAlone(){ return $this->scanAll('Alone'); } 339 | protected function metAlpha(){ return $this->scanAll('Alpha'); } 340 | protected function metAlphaNumeric(){ return $this->scanAll('AlphaNumeric'); } 341 | protected function metAlphaDash(){ return $this->scanAll('AlphaDash'); } 342 | protected function metAlphaUnderscore(){ return $this->scanAll('AlphaUnderscore'); } 343 | protected function metArraylist(){ switch($this->meetArraylist){ case 'all': return $this->scanAll('Arraylist'); break; case 'one': return $this->scanOne('Arraylist'); break; } } 344 | protected function metBase64(){ return $this->scanAll('Base64'); } 345 | protected function metBetween(){ return $this->scanAll('Between'); } 346 | protected function metBoolean(){ return $this->scanAll('Boolean'); } 347 | protected function metCount(){ return $this->scanOne('Count'); } 348 | protected function metDate(){ return $this->scanAll('Date'); } 349 | protected function metDatetime(){ return $this->scanAll('Datetime'); } 350 | protected function metDecimal(){ return $this->scanAll('Decimal'); } 351 | protected function metEarlier(){ return $this->scanAll('Earlier'); } 352 | protected function metEmail(){ return $this->scanAll('Email'); } 353 | protected function metEquals(){ return $this->scanAll('Equals'); } 354 | protected function metFile(){ return $this->scanAll('File'); } 355 | protected function metGreaterThan(){ return $this->scanAll('GreaterThan'); } 356 | protected function metId(){ return $this->scanAll('Id'); } 357 | protected function metIn(){ return $this->scanAll('In'); } 358 | protected function metInteger(){ return $this->scanAll('Integer'); } 359 | protected function metIp(){ return $this->scanAll('Ip'); } 360 | protected function metLater(){ return $this->scanAll('Later'); } 361 | protected function metLength(){ return $this->scanAll('Length'); } 362 | protected function metLessThan(){ return $this->scanAll('LessThan'); } 363 | protected function metMany(){ return $this->scanOne('Many'); } 364 | protected function metNatural(){ return $this->scanAll('Natural'); } 365 | protected function metNaturalPositive(){ return $this->scanAll('NaturalPositive'); } 366 | protected function metNone(){ return $this->scanOne('None'); } 367 | protected function metNot(){ return $this->scanAll('Not'); } 368 | protected function metNull(){ return $this->scanAll('Null'); } 369 | protected function metNumeric(){ return $this->scanAll('Numeric'); } 370 | protected function metObject(){ return $this->scanAll('Object'); } 371 | protected function metOne(){ return $this->scanOne('One'); } 372 | protected function metOptional(){ return $this->scanOne('Optional'); } 373 | protected function metPhone(){ return $this->scanAll('Phone'); } 374 | protected function metRequired(){ return $this->scanOne('Required'); } 375 | protected function metRow(){ switch($this->meetRow){ case 'all': return $this->scanAll('Row'); break; case 'one': return $this->scanOne('Row'); break; } } 376 | protected function metString(){ return $this->scanAll('String'); } 377 | protected function metTime(){ return $this->scanAll('Time'); } 378 | protected function metUrl(){ return $this->scanAll('Url'); } 379 | protected function metWithData(){ return $this->scanOne('WithData'); } 380 | 381 | public function parent($recursion = false){ 382 | 383 | return $this->getParent($recursion); 384 | 385 | } 386 | 387 | protected function predicateAllowed($value, $key){ if (is_null($this->parent) || !($this->parent instanceof self)) return false; return in_array($this->getName(), $this->parent->data(self::DATA_ALLOWABLE)); } 388 | protected function predicateAlone($value){ if (is_null($this->parent) || !($this->parent instanceof self)) return true; $parentData = $this->parent->getData(self::DATA_ORIGINAL); if (!is_array($parentData)) return true; if ($this->meetAlone === true) return (count($parentData) == 1 && array_key_exists($this->getName(), $parentData)); $parentDataAlone = array_diff_key($parentData, array_flip($this->meetAlone)); return (count($parentDataAlone) == 1 && array_key_exists($this->getName(), $parentDataAlone)); } 389 | protected function predicateAlpha($value){ return (bool) preg_match('/[a-zA-Z]+/', $value); } 390 | protected function predicateAlphaNumeric($value){ return (bool) preg_match('/[a-zA-Z0-9]+/', $value); } 391 | protected function predicateAlphaDash($value){ return (bool) preg_match('/[a-zA-Z0-9-]+/', $value); } 392 | protected function predicateAlphaUnderscore($value){ return (bool) preg_match('/[a-z0-9_]+/', $value); } 393 | protected function predicateArraylist($value){ return is_array($value); } 394 | protected function predicateBase64($value){ return (bool) !preg_match('/[^a-zA-Z0-9\/\+=]/', $value); } 395 | protected function predicateBetween($value){ return $value >= $this->meetBetween[0] && $value <= $this->meetBetween[1]; } 396 | protected function predicateBoolean($value){ return ($this->meetBoolean == 'strict') ? is_bool($value) : is_bool($value) || $value === 0 || $value === 1 || $value === '0' || $value === '1'; } 397 | protected function predicateCount($value){ $count = count($value); if (!is_null($this->meetCount[0]) && !is_null($this->meetCount[1])){ if (is_numeric($this->meetCount[0]) && is_numeric($this->meetCount[1])) return ($count >= $this->meetCount[0] && $count <= $this->meetCount[1]); if (is_numeric($this->meetCount[0])) return $count >= $this->meetCount[0]; else return $count <= $this->meetCount[1]; } else if (!is_null($this->meetCount[0])) return $count == $this->meetCount[0]; return false; } 398 | protected function predicateDate($value){ return (bool) preg_match('/^(((\d{4})(-)(0[13578]|10|12)(-)(0[1-9]|[12][0-9]|3[01]))|((\d{4})(-)(0[469]|1??1)(-)([0][1-9]|[12][0-9]|30))|((\d{4})(-)(02)(-)(0[1-9]|1[0-9]|2[0-8]))|(([02468]??[048]00)(-)(02)(-)(29))|(([13579][26]00)(-)(02)(-)(29))|(([0-9][0-9][0][48])(-)(0??2)(-)(29))|(([0-9][0-9][2468][048])(-)(02)(-)(29))|(([0-9][0-9][13579][26])(-)(02??)(-)(29)))$/', $value); } 399 | protected function predicateDatetime($value){ return (bool) preg_match('/^(((\d{4})(-)(0[13578]|10|12)(-)(0[1-9]|[12][0-9]|3[01]))|((\d{4})(-)(0[469]|1??1)(-)([0][1-9]|[12][0-9]|30))|((\d{4})(-)(02)(-)(0[1-9]|1[0-9]|2[0-8]))|(([02468]??[048]00)(-)(02)(-)(29))|(([13579][26]00)(-)(02)(-)(29))|(([0-9][0-9][0][48])(-)(0??2)(-)(29))|(([0-9][0-9][2468][048])(-)(02)(-)(29))|(([0-9][0-9][13579][26])(-)(02??)(-)(29)))(\s([0-1][0-9]|2[0-4]):([0-5][0-9]):([0-5][0-9]))$/', $value); } 400 | protected function predicateDecimal($value){ return filter_var($value, FILTER_VALIDATE_FLOAT) !== false; } 401 | protected function predicateEarlier($value){ return strtotime($value) < $this->meetEarlier; } 402 | protected function predicateEmail($value){ return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; } 403 | protected function predicateEquals($value){ return $value == $this->meetEquals; } 404 | protected function predicateFile($value){ return is_file($value); } 405 | protected function predicateGreaterThan($value){ return $value > $this->meetGreaterThan; } 406 | protected function predicateId($value){ return (bool) preg_match('/^[0-9]+$/', $value) && $value > 0; } 407 | protected function predicateIn($value){ return in_array($value, $this->meetIn); } 408 | protected function predicateInteger($value){ return filter_var($value, FILTER_VALIDATE_INT) !== false; } 409 | protected function predicateIp($value){ return filter_var($value, FILTER_VALIDATE_IP) !== false; } 410 | protected function predicateLater($value){ return strtotime($value) > $this->meetLater; } 411 | protected function predicateLength($value){ $length = strlen($value); if (!is_null($this->meetLength[0]) && !is_null($this->meetLength[1])){ if ($this->meetLength[0] !== false && $this->meetLength[1] !== false) return ($length >= $this->meetLength[0] && $length <= $this->meetLength[1]); if ($this->meetLength[0] !== false) return $length >= $this->meetLength[0]; return $length <= $this->meetLength[1]; } else if (!is_null($this->meetLength[0])) return $length == $this->meetLength[0]; return false; } 412 | protected function predicateLessThan($value){ return $value < $this->meetLessThan; } 413 | protected function predicateMany($value){ return count($value) > 1; } 414 | protected function predicateNatural($value){ return (bool) preg_match('/^[0-9]+$/', $value); } 415 | protected function predicateNaturalPositive($value){ return (bool) preg_match('/^[0-9]+$/', $value) && $value > 0; } 416 | protected function predicateNone($value){ return empty($this->data); } 417 | protected function predicateNot($value){ return (is_array($this->meetNot)) ? !in_array($value, $this->meetNot) : $value != $this->meetNot; } 418 | protected function predicateNull($value){ return is_null($value); } 419 | protected function predicateNumeric($value){ return is_numeric($value); } 420 | protected function predicateObject($value){ return ($this->meetObject === true) ? is_object($value) : is_object($value) && $value instanceof $this->meetObject; } 421 | protected function predicateOne($value){ return count($value) == 1; } 422 | protected function predicateOptional($value){ return true; } 423 | protected function predicatePhone($value){ if ($this->meetPhone == 'strict') return (bool) preg_match('/^\([0-9]{3}\)\s?[0-9]{3}-[0-9]{4}$/', $value); else return (strlen(preg_replace('/[^0-9]/', '', $value)) >= 10); } 424 | protected function predicateRequired($value){ if (is_array($this->data)){ if (is_array($this->meetRequired)) foreach ($this->meetRequired as $required) if (empty($this->data[$required])) return false; return !empty($this->data); } return (trim($this->data) != ''); } 425 | protected function predicateRow($value){ return (isset($value['id']) && preg_match('/^[0-9]+$/', $value['id']) && $value['id'] > 0); } 426 | protected function predicateString($value){ return is_string($value); } 427 | protected function predicateTime($value){ return (bool) preg_match('/^(([0-1][0-9]|2[0-4]):([0-5][0-9]):([0-5][0-9]))$/', $value); } 428 | protected function predicateURL($value){ return filter_var($value, FILTER_VALIDATE_URL) !== false; } 429 | protected function predicateWithData($value){ $data = $this->data(self::DATA_ORIGINAL); return !empty($data); } 430 | 431 | protected function scanAll($predicate){ 432 | 433 | $mets = array(); 434 | $predicateMethod = 'predicate' . $predicate; 435 | $predicateFullName = $this->getPredicateFullName($predicate); 436 | 437 | if (!is_array($this->data)){ 438 | 439 | $value = $this->data; 440 | $met = call_user_func_array(array($this, $predicateMethod), array($value, $this->getName())); 441 | $mets[$predicateFullName] = array('term' => $this->getFullName(), 'name' => $this->getName(), 'value' => $value, 'predicate' => $predicate, 'met' => $met); 442 | 443 | } 444 | else foreach ($this->data as $key => $value){ 445 | 446 | $met = call_user_func_array(array($this, $predicateMethod), array($value, $key)); 447 | $mets[$predicateFullName] = array('term' => $this->getFullName(), 'name' => $key, 'value' => $value, 'predicate' => $predicate, 'met' => $met); 448 | 449 | } 450 | 451 | return $mets; 452 | 453 | } 454 | 455 | protected function scanOne($predicate){ 456 | 457 | $mets = array(); 458 | $predicateMethod = 'predicate' . $predicate; 459 | $predicateFullName = $this->getPredicateFullName($predicate); 460 | 461 | $value = $this->data; 462 | $met = call_user_func_array(array($this, $predicateMethod), array($value, $this->getName())); 463 | $mets[$predicateFullName] = array('term' => $this->getFullName(), 'name' => $this->getName(), 'value' => $value, 'predicate' => $predicate, 'met' => $met); 464 | 465 | return $mets; 466 | 467 | } 468 | 469 | protected function setChildren(){ 470 | 471 | if (is_array($this->data)) foreach ($this->data as $name => $data) $this->children[$name] = new Contract_Term($name, $data, $this); 472 | 473 | } 474 | 475 | protected function setData($data = null){ 476 | 477 | $this->data = $data; 478 | 479 | } 480 | 481 | protected function setName($name){ 482 | 483 | if (is_null($this->name)) $this->name = $name; 484 | 485 | } 486 | 487 | protected function setParent($parent){ 488 | 489 | if ($parent instanceof Contract || $parent instanceof Contract_Term) $this->parent = $parent; 490 | 491 | } 492 | 493 | 494 | public function __toString(){ 495 | 496 | $string = '[term:' . $this->getFullName() . "]\n"; 497 | $vars = get_object_vars($this); 498 | foreach ($vars as $key => $value) if (substr($key, 0, 4) == 'meet' && $value !== false) $string .= "\t" . $key . "\n"; 499 | foreach ($this->children as $term) $string .= $term; 500 | return $string; 501 | 502 | } 503 | 504 | } 505 | 506 | ?> 507 | -------------------------------------------------------------------------------- /Contract/Term/Abstract.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Contract/Term/ProxyCollection.php: -------------------------------------------------------------------------------- 1 | parent = $parent; 12 | $parentData = $parent->getData(Contract_Term::DATA_ORIGINAL); 13 | if (!is_array($parentData)) throw new Exception('Array expected for parent data.'); 14 | 15 | } 16 | 17 | public function __call($method, $arguments){ 18 | 19 | if (is_null($this->returnTerms)){ 20 | 21 | if ($method == 'end') return $this->parent; 22 | 23 | $parentData = $this->parent->data(Contract_Term::DATA_ORIGINAL); 24 | $returnTerms = array(); 25 | 26 | foreach ($parentData as $key => $value){ 27 | 28 | $parentTerm = $this->parent->element($key); 29 | $returnTerms[] = call_user_func_array(array($parentTerm, $method), $arguments); 30 | 31 | } 32 | 33 | if ($method == 'element') $this->returnTerms = $returnTerms; 34 | 35 | } 36 | else { 37 | 38 | if ($method == 'end') $this->returnTerms = null; 39 | else foreach ($this->returnTerms as $returnTerm) call_user_func_array(array($returnTerm, $method), $arguments); 40 | 41 | } 42 | 43 | return $this; 44 | 45 | } 46 | 47 | } 48 | 49 | ?> -------------------------------------------------------------------------------- /Examples/Array_Notation.php: -------------------------------------------------------------------------------- 1 | test(); 5 | 6 | class Array_Notation { 7 | 8 | public function test(){ 9 | 10 | $this->testA('2053', 'John Smith'); 11 | $this->testB(); 12 | 13 | } 14 | 15 | /* Validating Class Method Parameters In Array Notation */ 16 | public function testA($userId, $userName){ 17 | 18 | $contract = new Contract(array( 19 | 'userId' => 'id', 20 | 'userName' => array('optional', 'alphaNumeric', 'length' => array(8,12)) 21 | )); 22 | $contract->metOrThrow(); 23 | 24 | } 25 | 26 | 27 | /* Validating Local Variables In Array Notation */ 28 | public function testB(){ 29 | 30 | $testValue1 = 100; 31 | $testValue2 = 'red'; 32 | $testValue3 = array('name' => 'John Smith', 'age' => 30); 33 | 34 | $contract = new Contract(array( 35 | 'test1' => array('data' => $testValue1, 'definition' => 'integer'), 36 | 'test2' => array('data' => $testValue2, 'definition' => array('length' => 3, 'in' => array('red', 'green', 'blue'))), 37 | 'test3' => array('data' => $testValue3, 'definition' => array('arraylist', 'element' => array('age' => array('integer', 'lessThan' => 29), 'name' => array('alpha', 'in' => array('John Doe', 'Jane Smith'))))) 38 | )); 39 | $contract->metOrThrow(); 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Examples/Controller.php: -------------------------------------------------------------------------------- 1 | term('userEmail', $loginUserEmail)->email()->metOrThrow(); 19 | $contract->term('userPass', $loginUserPass)->alphaNumeric()->length(8,16)->metOrThrow(); 20 | $contract->term('userIp', $loginUserPass)->ip()->metOrThrow(); 21 | $contract->term('dateTime', $loginDateTime)->datetime()->metOrThrow(); 22 | 23 | /* Get User For Login */ 24 | $user = $userModel->getUser($userEmail, $userPass); 25 | $contract->term('user', $user)->arraylist() 26 | ->element('id')->id()->end() 27 | ->element('active')->equals(1)->end() 28 | ->metOrThrow(); 29 | $loginUserId = $user['id']; 30 | 31 | /* Proceed Safely to Model for Storage of User Login */ 32 | $logged = $userModel->login($loginUserId, $loginUserIp, $loginDateTime); 33 | $contract->term('userLogged', $logged)->boolean()->equals(TRUE)->metOrThrow(); 34 | 35 | } 36 | catch (Contract_Exception $e){ 37 | 38 | /* Collect error messages from contract exception */ 39 | $messages = array(); 40 | switch ($e->term){ 41 | 42 | case 'userEmail': $messages[] = 'Please enter an email address.'; break; 43 | case 'userPass': $messages[] = 'Please enter a password.'; break; 44 | case 'userIp': $messages[] = 'Please enter a valid ip address.'; break; 45 | case 'dateTime': $messages[] = 'Please enter a valid date time.'; break; 46 | case 'user': $messages[] = 'Please enter a valid user.'; break; 47 | case 'userLogged': $messages[] = 'Sorry. You could not be logged in.'; break; 48 | default: $messages[] = 'We do not get it either!'; break; 49 | 50 | } 51 | 52 | } 53 | 54 | } 55 | 56 | } 57 | 58 | } 59 | 60 | ?> 61 | -------------------------------------------------------------------------------- /Examples/Filter.php: -------------------------------------------------------------------------------- 1 | test(); 5 | 6 | class User_Filter { 7 | 8 | public function test(){ 9 | 10 | $usersData = self::getUsersData(); 11 | 12 | $usersDataFilteredByStateCT = $this->filterByState($usersData, 'CT'); 13 | print_r($usersDataFilteredByStateCT); 14 | 15 | $usersDataFilteredByHasMultipleOptions = $this->filterByHasMultipleOptions($usersData); 16 | print_r($usersDataFilteredByHasMultipleOptions); 17 | 18 | $usersDataFilteredByValidUser = $this->filterByValidUser($usersData); 19 | print_r($usersDataFilteredByValidUser); 20 | 21 | } 22 | 23 | public function filterByHasMultipleOptions($usersData){ 24 | 25 | /* Before filtering the data, establish the agreement for the method parameters. */ 26 | $contract = new Contract(); 27 | $contract->term('usersData')->arraylist(); 28 | $contract->metOrThrow(); 29 | 30 | /* Users data must be an array of elements, each element an array itself, with an options array that has two or more elements. */ 31 | $contract->term('usersData')->elements() 32 | ->arraylist() 33 | ->allowed('*') 34 | ->element('options')->arraylist()->count(2, '*'); 35 | 36 | $filteredData = $contract->term('usersData')->data(); 37 | 38 | return $filteredData; 39 | 40 | } 41 | 42 | public function filterByState($usersData, $state){ 43 | 44 | /* Before filtering the data, establish the agreement for the method parameters. */ 45 | $contract = new Contract(); 46 | $contract->term('usersData')->arraylist(); 47 | $contract->term('state')->length(2)->in(self::getStates(true)); 48 | $contract->metOrThrow(); 49 | 50 | /* Users data must be an array of elements, each element an array itself, with a state element equaling $state. 51 | Allowed fields to be returned are name, address, city, state, and zip. */ 52 | $contract->term('usersData')->elements() 53 | ->arraylist() 54 | ->allowed(array('name', 'address', 'city', 'state', 'zip')) 55 | ->element('state')->equals($state)->end(); 56 | 57 | $filteredData = $contract->term('usersData')->data(); 58 | 59 | return $filteredData; 60 | 61 | } 62 | 63 | public function filterByValidUser($usersData){ 64 | 65 | /* Before filtering the data, establish the agreement for the method parameters. */ 66 | $contract = new Contract(); 67 | $contract->term('usersData')->arraylist(); 68 | $contract->metOrThrow(); 69 | 70 | /* Users data must be an array of elements, each element an array itself, with an id, name, registered date, and active boolean true. 71 | Allowed fields to be returned are the defined elements: id, name, registered, active. */ 72 | $contract->term('usersData')->elements() 73 | ->arraylist() 74 | ->element('id')->id()->end() 75 | ->element('name')->optional()->alpha()->end() 76 | ->element('registered')->datetime()->end() 77 | ->element('active')->boolean()->equals(true)->end(); 78 | 79 | $filteredData = $contract->term('usersData')->data(); 80 | 81 | return $filteredData; 82 | 83 | } 84 | 85 | public static function getStates($abbreviationIsKey = false){ 86 | 87 | $states = array( 88 | 'AL' => 'Alabama', 89 | 'AK' => 'Alaska', 90 | 'AZ' => 'Arizona', 91 | 'AR' => 'Arkansas', 92 | 'CA' => 'California', 93 | 'CO' => 'Colorado', 94 | 'CT' => 'Connecticut', 95 | 'DE' => 'Delaware', 96 | 'DC' => 'District Of Columbia', 97 | 'FL' => 'Florida', 98 | 'GA' => 'Georgia', 99 | 'HI' => 'Hawaii', 100 | 'ID' => 'Idaho', 101 | 'IL' => 'Illinois', 102 | 'IN' => 'Indiana', 103 | 'IA' => 'Iowa', 104 | 'KS' => 'Kansas', 105 | 'KY' => 'Kentucky', 106 | 'LA' => 'Louisiana', 107 | 'ME' => 'Maine', 108 | 'MD' => 'Maryland', 109 | 'MA' => 'Massachusetts', 110 | 'MI' => 'Michigan', 111 | 'MN' => 'Minnesota', 112 | 'MS' => 'Mississippi', 113 | 'MO' => 'Missouri', 114 | 'MT' => 'Montana', 115 | 'NE' => 'Nebraska', 116 | 'NV' => 'Nevada', 117 | 'NH' => 'New Hampshire', 118 | 'NJ' => 'New Jersey', 119 | 'NM' => 'New Mexico', 120 | 'NY' => 'New York', 121 | 'NC' => 'North Carolina', 122 | 'ND' => 'North Dakota', 123 | 'OH' => 'Ohio', 124 | 'OK' => 'Oklahoma', 125 | 'OR' => 'Oregon', 126 | 'PA' => 'Pennsylvania', 127 | 'RI' => 'Rhode Island', 128 | 'SC' => 'South Carolina', 129 | 'SD' => 'South Dakota', 130 | 'TN' => 'Tennessee', 131 | 'TX' => 'Texas', 132 | 'UT' => 'Utah', 133 | 'VT' => 'Vermont', 134 | 'VA' => 'Virginia', 135 | 'WA' => 'Washington', 136 | 'WV' => 'West Virginia', 137 | 'WI' => 'Wisconsin', 138 | 'WY' => 'Wyoming' 139 | ); 140 | 141 | if ($abbreviationIsKey) $states = array_flip($states); 142 | 143 | return $states; 144 | 145 | } 146 | 147 | public static function getUsersData(){ 148 | 149 | $usersData = array( 150 | array( 151 | 'id' => 1, 152 | 'name' => 'John Smith', 153 | 'address' => '123 Main Street', 154 | 'city' => 'Hartford', 155 | 'state' => 'CT', 156 | 'zip' => '34678', 157 | 'options' => array( 158 | 'remember' => true, 159 | 'duration_length' => 2, 160 | 'duration_unit' => 'week' 161 | ), 162 | 'logged' => '2014-02-06 12:00:00', 163 | 'registered' => '2012-01-01 12:00:00', 164 | 'active' => true 165 | ), 166 | array( 167 | 'id' => 2, 168 | 'name' => 'Jane Smith', 169 | 'address' => '345 Main Street', 170 | 'city' => 'Boston', 171 | 'state' => 'MA', 172 | 'zip' => '01243', 173 | 'options' => array( 174 | 'remember' => false 175 | ), 176 | 'logged' => '2014-03-01 12:00:00', 177 | 'registered' => '2012-07-01 12:00:00', 178 | 'active' => true 179 | ), 180 | array( 181 | 'id' => null, 182 | 'name' => 'Paul Smith', 183 | 'address' => '768 First Street', 184 | 'city' => 'Providence', 185 | 'state' => 'RI', 186 | 'zip' => '03654', 187 | 'options' => array( 188 | 'remember' => null 189 | ), 190 | 'logged' => null, 191 | 'registered' => '2012-05-01 12:00:00', 192 | 'active' => false 193 | ) 194 | ); 195 | 196 | return $usersData; 197 | 198 | } 199 | 200 | } 201 | 202 | ?> 203 | -------------------------------------------------------------------------------- /Examples/Find.php: -------------------------------------------------------------------------------- 1 | array( 9 | 'inner' => array( 10 | 'number' => 1, 11 | 'letter' => 'A' 12 | ), 13 | 'inner2' => array( 14 | 'email' => 'user@email.com' 15 | ), 16 | ) 17 | ); 18 | 19 | /* Run the tests */ 20 | $this->children($data); 21 | $this->parents($data); 22 | 23 | } 24 | 25 | /* Direct access for defining child terms found anywhere in multi-dimensional arrays */ 26 | public function children($data){ 27 | 28 | $contract = new Contract(); 29 | $contract->term('data/outer/inner')->arraylist() 30 | ->element('number')->integer()->end() 31 | ->element('letter')->alpha()->end(); 32 | $contract->metOrThrow(); 33 | 34 | } 35 | 36 | /* Direct access for defining parent terms from anywhere in multi-dimensional arrays */ 37 | public function parents($data){ 38 | 39 | $contract = new Contract(); 40 | $contract->term('data')->arraylist() 41 | ->element('outer')->arraylist() 42 | ->element('inner')->arraylist() 43 | ->element('number')->integer()->parent() 44 | ->element('letter')->alpha()->parent('outer') /* Direct access to 'outer' element */ 45 | ->element('inner2')->arraylist() 46 | ->element('email')->email()->parent(TRUE) /* Direct access to the contract super object */ 47 | ->metOrThrow(); 48 | 49 | } 50 | 51 | } 52 | 53 | ?> 54 | -------------------------------------------------------------------------------- /Examples/Image_Resize.php: -------------------------------------------------------------------------------- 1 | array( 9 | 'style' => array( 10 | 'top' => 0, 11 | 'left' => 0, 12 | 'width' => 0, 13 | 'height' => 0 14 | ), 15 | 'thumb' => array( 16 | 'style' => array( 17 | 'top' => 0, 18 | 'left' => 0, 19 | 'width' => 0, 20 | 'height' => 0 21 | ) 22 | ) 23 | ) 24 | ); 25 | 26 | try { 27 | 28 | /* The versatility in checking multi-dimensional arrays is profoundly intuitive! */ 29 | $contract = new Site_Contract(); 30 | $contract->term('fileData', $fileData)->arraylist() 31 | ->element('options')->arraylist() 32 | ->element('style')->arraylist() 33 | ->element('top')->natural()->end() 34 | ->element('left')->natural()->end() 35 | ->end() 36 | ->element('thumb')->arraylist() 37 | ->element('style')->arraylist() 38 | ->element('top')->natural()->end() 39 | ->element('left')->natural()->end() 40 | ->end(); 41 | $contract->metOrThrow(); 42 | 43 | 44 | } 45 | catch(Exception $exception){ 46 | 47 | echo $exception->term; 48 | 49 | } 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Examples/Model.php: -------------------------------------------------------------------------------- 1 | term('userId')->id(); 10 | $contract->term('userData')->arraylist() 11 | ->element('type')->in(array('member','administrator'))->end() 12 | ->element('username')->alphaNumeric()->length(8, 16)->end() 13 | ->element('name')->required()->end() 14 | ->element('address')->required()->end() 15 | ->element('city')->required()->end() 16 | ->element('state')->length(2)->end() 17 | ->element('zip')->length(5,10)->end() 18 | ->element('country')->required()->end() 19 | ->element('email')->email()->end() 20 | ->element('phone')->phone()->end() 21 | ->element('fax')->optional()->phone()->end() 22 | ->element('photo')->optional()->file()->end() 23 | ->element('website')->optional()->url()->end() 24 | ->element('registered')->datetime()->end() 25 | ->element('active')->boolean()->end(); 26 | $contract->metOrThrow(); 27 | 28 | /* Follow w/ Basic MySQL Query */ 29 | $rows = array(); 30 | 31 | /* $select = "SELECT * FROM user WHERE id = {$userId}"; 32 | while($row = mysql_query($select)) $rows[] = $row; */ 33 | 34 | return $rows; 35 | 36 | } 37 | 38 | } 39 | 40 | ?> 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Overview.txt: -------------------------------------------------------------------------------- 1 | Created in 2014, PHP-Contract is the answer to the lack of type hinting capabilities in object-oriented PHP. 2 | 3 | The new library is simple to use and has powerful results, including verifying the types of arguments being passed 4 | through class methods. The list of supported types is grand, and includes checks for scalar types, object, and various 5 | common data formats, such as multi-dimensional arrays and single values such as ids, datetime, email, and phone strings. 6 | 7 | PHP-Contract is a formal introduction to forming agreements between your application and its class methods, in order to 8 | ensure the proper flow and function inside of each individual scope. The class offers method chaining for a simple and 9 | sleek integration within your existing code, and the power of the code can make a big impact on the size and stability 10 | of your checks. 11 | 12 | Beyond advanced checking, this one-of-a-kind library filters data at a granular level, and includes support for advanced 13 | filtering of data in multi-dimensional arrays. For example, have you ever had a large array and you wanted to return 14 | only a portion of the data? Maybe you have had to scan deeply into a multi-dimensional array to find information? And 15 | sometimes even the cleanest approach will require too many lines of code and iterations through loops; when this happens, 16 | the elegance of the code disappears. With PHP-Contract, the answer is simple: define your terms and get only the data 17 | you want instantly, and elegantly, without reinventing the wheel. 18 | 19 | While PHP-Contract is designed to ensure the integrity of values passed to class method parameters, it is also useful in 20 | MVC architectures to regulate controller actions and verify access to model methods. 21 | 22 | Forget about type hinting and get into these contracts! 23 | 24 | Download it here: https://github.com/axiom82/PHP-Contract 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP-Contract 2 | ============ 3 | 4 | A PHP library designed to ensure the integrity of values passed to class method parameters. 5 | 6 | 7 | Example of Chaining Contract Terms and Rules 8 | ---------------------------------------------------------- 9 | 10 |
 11 | class Model {
 12 | 
 13 | 	public function getFoos($barId, $includeBaz = false, $limit = 0, $offset = 0){
 14 | 	
 15 | 		$contract = new Contract();
 16 | 		$contract->term('barId')->id()->end()
 17 | 				 ->term('includeBaz')->boolean()->end()
 18 | 				 ->term('limit')->natural()->end()
 19 | 				 ->term('offset')->natural()->end()
 20 | 				 ->metOrThrow();
 21 | 			 
 22 | 		/* Continue with peace of mind ... */
 23 | 
 24 | 	}
 25 | 	
 26 | }
 27 | 
28 | 29 | Example of Array Notation for Defining Contract Terms 30 | ----------------------------------------------------------- 31 | 32 |
 33 | class UserModel {
 34 | 
 35 | 	public function getUser($userId, $userName){
 36 | 
 37 | 		$contract = new Contract(array(
 38 | 			'userId' => 'id',
 39 | 			'userName' => array('optional', 'alphaNumeric', 'length' => array(8,12))
 40 | 		));
 41 | 		$contract->metOrThrow();
 42 | 		
 43 | 		/* Now, get the user ... */
 44 | 
 45 | 	}
 46 | 	
 47 | }
 48 | 
49 | 50 | In PHP > 5.4, you can take advantage of this additional short coding for defining contract terms via array notation: 51 | ------------------------------------------------------------------------------------------------------------------- 52 | 53 |
 54 | $contract = new Contract([
 55 |     'number1' => 'integer',
 56 |     'number2' => ['integer', 'between' => [1, 50]],
 57 |     'number3' => ['optional', 'integer', 'lessThan' => 15]
 58 | ]);
 59 | $contract->metOrThrow();
 60 | 
61 | 62 | Example of Checking The Contract During Initialization: 63 | ------------------------------------------------------------------------------------------------------------------- 64 | 65 |
 66 | $contract = new Contract(array(
 67 |     'userId'    => 'id',
 68 |     'userData'  => array('arraylist', 'element' => array('name' => 'alpha', 'address' => 'required'))
 69 | ), Contract::MET_OR_THROW);
 70 | 
71 | 72 | Documentation of PHP-Contract Functionality 73 | ------------------------------------------- 74 | (For detailed usage examples, see https://github.com/axiom82/PHP-Contract/tree/master/Examples, and for more information, see https://github.com/axiom82/PHP-Contract/wiki) 75 | 76 | 77 |
 78 | 
 79 | class MyClass {
 80 | 
 81 | 	public function myMethod($arg){
 82 | 
 83 | 		$contract = new Contract();
 84 | 		
 85 | 		/* Defining Terms For Method Arguments and Local Variables in Method Scope ...
 86 | 		   Note: arguments are already created as terms for you, local variables must be created manually (see below) */
 87 | 		
 88 | 		$contract->term('arg')->allowed($array); /* The term may be an array containing the specified fields (other fields filtered out, see data();) */
 89 | 		$contract->term('arg')->alone(); /* The term must be alone, having no siblings */
 90 | 		$contract->term('arg')->alpha(); /* The term must be an alphabetical string */
 91 | 		$contract->term('arg')->alphaNumeric(); /* The term must be an alplanumeric string */
 92 | 		$contract->term('arg')->alphaDash(); /* The term must be an alphanumeric allowing dashes */
 93 | 		$contract->term('arg')->alphaUnderscore(); /* The term must be an alphanumeric allowing unscores */
 94 | 		$contract->term('arg')->arraylist(); /* The term must be an array */
 95 | 		$contract->term('arg')->base64(); /* The term must be a base64 string */
 96 | 		$contract->term('arg')->between($value, $value2); /* The term must be between the range of the two values */
 97 | 		$contract->term('arg')->boolean(); /* The term must be a boolean */
 98 | 		$contract->term('arg')->count($value); /* The term must be the count of the value (for arrays) */
 99 | 		$contract->term('arg')->decimal(); /* The term must be a decimal */
100 | 		$contract->term('arg')->earlier($value); /* The term must be earlier than the value */
101 | 		$contract->term('arg')->email(); /* The term must be an email address */
102 | 		$contract->term('arg')->equals($value); /* The term must match the value */
103 | 		$contract->term('arg')->greaterThan($value); /* The term must be greater than the value */
104 | 		$contract->term('arg')->id(); /* The term must be an id (a natural positive number) */
105 | 		$contract->term('arg')->in($value); /* The term must be in the values of the array */
106 | 		$contract->term('arg')->integer(); /* The term must be an integer */
107 | 		$contract->term('arg')->ip(); /* The term must be an ip address */
108 | 		$contract->term('arg')->later($value); /* The term must be later than the value */
109 | 		$contract->term('arg')->length($value); /* The term must be the length of the value */
110 | 		$contract->term('arg')->lessThan($value); /* The term must be less than the value */
111 | 		$contract->term('arg')->many(); /* The term must be an array with more than one element */
112 | 		$contract->term('arg')->natural(); /* The term must be a natural number */
113 | 		$contract->term('arg')->naturalPositive(); /* The term must be a natural positive number */
114 | 		$contract->term('arg')->none(); /* The term must be an empty value or values */
115 | 		$contract->term('arg')->not($value); /* The term must not be equal to the value or values */
116 | 		$contract->term('arg')->null(); /* The term must be null */
117 | 		$contract->term('arg')->numeric(); /* The term must be numeric */
118 | 		$contract->term('arg')->object($value); /* The term must be an object that is an instance of the value */
119 | 		$contract->term('arg')->one(); /* The term must be an array with one and only one element */
120 | 		$contract->term('arg')->optional(); /* The term is not required */
121 | 		$contract->term('arg')->phone(); /* The term must be a phone number */
122 | 		$contract->term('arg')->required(); /* The term must be non-empty */
123 | 		$contract->term('arg')->required($values); /* The term must be an array with the specific fields */
124 | 		$contract->term('arg')->string(); /* The term must be a string */
125 | 		$contract->term('arg')->url(); /* The term must be URL */
126 | 		$contract->term('arg')->withData(); /* The term, after the contract filters out invalid data,
127 | 		                                       must have one or more valid values */
128 | 		
129 | 		/* Defining Terms for Local Variables in Method Scope */
130 | 		$contract->term('id', $idVar)->id();
131 | 		$contract->term('email', $emailVar)->email();
132 | 		$contract->term('password', $passVar)->alphaNumeric()->length(8, 16);
133 | 
134 | 		/* Validation */
135 | 		$met = $contract->term('arg')->met(); /* The contract term has a met() method that checks to see if
136 | 		                                         the term met its own rules, it does so and then returns a 
137 | 		                                         boolean for success or failure */
138 | 		if (!$met) return false; /* You may choose to return false when the term has not been met */
139 | 		
140 | 		$met = $contract->met(); /* The contract checks all of its child terms through its met() method,
141 | 		                            which calls each contract term's met() method,
142 | 		                            and collects the results */
143 | 		$contract->metOrThrow(); /* Most useful I think.  Equivalent to met(), however, throws an exception
144 | 		                            halting the program unless caught. Terms also have their own individual
145 | 		                            metOrThrow() method, in case you want to test line by line per term. */
146 | 		
147 | 		/* Post Validation, Obtaining Filtered Data */
148 | 		$argData = $contract->term('arg')->data(); /* Returns the term's value(s) as per the contract.
149 | 		                                              Indeed, the contract presents through its data() method
150 | 		                                              only the data that meets the contract term rules. If
151 | 		                                              allowed() is used (see above), data() will return the
152 | 		                                              allowed value(s) from the original value(s) in the
153 | 		                                              argument */
154 | 							      
155 | 		$argData = $contract->data('arg'); /* This is equivalent in functionality to the line above, however,
156 | 		                                      this method is cleaner in appearance.  The contract proxies to
157 | 		                                      the term and gets the data via the term's data() method. */
158 | 
159 | 		/* Debugging: "Which term(s) did not meet the contract?" */
160 | 		$contract->debug();
161 | 		
162 | 		/* Or, return the array into your own variable */
163 | 		$debug = $contract->debug(true);
164 | 		
165 | 		/* Print and review the full definition of the contract */
166 | 		echo $contract; /* Prints a clean and readable text describing the contract and its terms */
167 | 		
168 | 	}
169 | 	
170 | }
171 | 
172 | --------------------------------------------------------------------------------