Last active
August 30, 2016 16:38
-
-
Save abiusx/15ebb19ce5291bd23b3af1f2eeb5be03 to your computer and use it in GitHub Desktop.
Type Checked PHP (methods, functions are not yet supported)
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 | |
class TypedPHPException extends Exception { | |
function __construct($msg,$file,$line) | |
{ | |
parent::__construct($msg); | |
$this->file=$file; | |
$this->line=$line; | |
} | |
} | |
class TypeMistmatchException extends TypedPHPException {}; | |
class TypeNotFoundException extends TypedPHPException {}; | |
function type_list($typestring,$file,$line) | |
{ | |
static $types=["bool","false","true","null","int","float","string","array"]; | |
$list=explode("|",$typestring); | |
$out=[]; | |
foreach ($list as $t) | |
{ | |
if (!in_array(strtolower($t), $types) and !class_exists($t)) | |
{ | |
throw new TypeNotFoundException("Type '{$t}' does not exist.",$file,$line); | |
continue; | |
} | |
$out[]=$t; | |
} | |
return $out; | |
} | |
function type_check_args($class,$method_name,$args) | |
{ | |
#TODO: need arg names to match via doc, change arg{$i} names to the actual arg names. | |
$r=new ReflectionMethod($class,$method_name); | |
$doc=$r->getDocComment(); | |
$line=$r->getStartLine(); | |
$file=$r->getFileName(); | |
$endline=$r->getEndLine(); | |
var_dump($doc); | |
if (!preg_match("/@return\s+(.*?)\s+/", $doc,$match)) | |
return false; | |
$types=type_list($match[1],$file,$line); | |
foreach ($types as $type) | |
if ((is_object($arg) and strtolower(get_class($arg))==strtolower($type))) return true; | |
elseif (strtolower(gettype($arg))==strtolower($type)) return true; | |
throw new TypeMistmatchException("Type of return argument '".gettype($arg). | |
"' does not match any of the allowed types: '".implode(" or ",$types)."'",$file,$endline); | |
return false; | |
} | |
function type_check_return($class,$method_name,$arg) | |
{ | |
$r=new ReflectionMethod($class,$method_name); | |
$doc=$r->getDocComment(); | |
$line=$r->getStartLine(); | |
$file=$r->getFileName(); | |
$endline=$r->getEndLine(); | |
if (!preg_match("/@return\s+(.*?)\s+/", $doc,$match)) | |
return false; | |
$types=type_list($match[1],$file,$line); | |
foreach ($types as $type) | |
if ((is_object($arg) and strtolower(get_class($arg))==strtolower($type))) return true; | |
elseif (strtolower(gettype($arg))==strtolower($type)) return true; | |
throw new TypeMistmatchException("Type of return argument '".gettype($arg). | |
"' does not match any of the allowed types: '".implode(" or ",$types)."'",$file,$endline); | |
return false; | |
} | |
function typed_autoload($class) | |
{ | |
if (strtolower(substr($class,0,5))=="typed") | |
{ | |
$class=substr($class,5); | |
if (!class_exists($class)) | |
{ | |
trigger_error("class {$class} not found"); | |
return false; | |
} | |
} | |
else | |
return false; | |
$reflection = new ReflectionClass($class); | |
$filename = $reflection->getFileName(); | |
$startline = $reflection->getStartLine(); // getStartLine() seems to start after the { | |
$endline = $reflection->getEndLine(); | |
$newclass="Typed".$class; | |
if (class_exists($newclass)) | |
if(file_exists($filename)) | |
{ | |
$contents = file($filename); | |
$class_code=array_slice($contents, $startline,$endline-$startline); | |
} | |
$methods=[]; | |
foreach ($reflection->getMethods() as $method_reflection) | |
{ | |
$method_code=$method_reflection.""; | |
$method_ispublic=preg_match("/Method \[(.*?) public method .*? \]/",$method_code); | |
if (!$method_ispublic) continue; | |
$method_isref=preg_match("/Method \[(.*?) method &.*? \]/",$method_code); | |
preg_match("/- Parameters\s+\[(.*?)]\s+/",$method_code,$match); | |
if ($match) | |
$paramcount=$match[1]; | |
else | |
$paramcount=0; | |
$method_class=$method_reflection->class; | |
$method_name=$method_reflection->name; | |
$args=$argsSig=[]; | |
for ($i=0;$i<$paramcount;++$i) | |
{ | |
$isref=preg_match("/Parameter #{$i} \[.*?&.*?\]/", $method_code); | |
$optional=preg_match("/Parameter #{$i} \[ <optional> .*? = (.*?) \]/", $method_code,$optional_val); | |
if ($optional) | |
$optional_val=$optional_val[1]; | |
// echo "isref:"; | |
// var_dump($isref); | |
// echo "optional:"; | |
// var_dump($optional); | |
// if ($optional) | |
// var_dump("optional value:",$optional_val); | |
$args[]=["name"=>"arg{$i}","isref"=>$isref,"optional"=>$optional,"optional_val"=>$optional?$optional_val:null]; | |
$sig=""; | |
if ($isref) | |
$sig="&"; | |
$sig.="\$arg{$i}"; | |
if ($optional) | |
$sig.="={$optional_val}"; | |
$argsSig[]=$sig; | |
} | |
// var_dump($paramcount); | |
// var_dump($args); | |
// echo($method_code); | |
if ($paramcount) | |
$args_forward=implode(",",array_map(function($x){ return "\$arg".$x;}, range(0,$paramcount-1))); | |
else | |
$args_forward=""; | |
if ($method_isref) | |
$method_isref="&"; | |
else | |
$method_isref=""; | |
$signature="function {$method_isref}{$method_name}(".implode(",",$argsSig).") { | |
\$this->type_check_args('{$method_class}','{$method_name}',[$args_forward]); | |
\$r={$method_class}::{$method_name}({$args_forward}); | |
\$this->type_check_return('{$method_class}','{$method_name}',\$r); | |
return \$r; | |
}"; | |
$methods[]=["class"=>$method_class,"name"=>$method_name,"signature"=>$signature]; | |
} | |
$methods_signature=implode("\n\t\t",array_map(function($x){return $x['signature'];}, $methods)); | |
$source_code=("class Typed{$class} extends $class { | |
function type_check_return(\$class,\$method_name,\$arg) | |
{ | |
type_check_return(\$class,\$method_name,\$arg); | |
} | |
function type_check_args(\$class,\$method_name,\$args) | |
{ | |
type_check_args(\$class,\$method_name,\$args); | |
} | |
{$methods_signature} | |
}"); | |
// var_dump($source_code); | |
eval($source_code); | |
return true; | |
} | |
spl_autoload_register("typed_autoload"); | |
const ZERO=0; | |
class World | |
{ | |
function &b() | |
{ | |
} | |
function a() | |
{ | |
} | |
} | |
class Hello extends World { | |
public $x; | |
function __construct(&$t) | |
{ | |
$this->x=$t; | |
} | |
private function p() {} | |
/** | |
* [a description] | |
* @param string $x [description] | |
* @return int [description] | |
*/ | |
function a($x='hello') | |
{ | |
echo $this->x++; | |
} | |
} | |
$t=5; | |
$x=new TypedHello($t); | |
$x->a(); | |
$x->a(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment