Skip to content

Instantly share code, notes, and snippets.

@abiusx
Last active August 30, 2016 16:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abiusx/15ebb19ce5291bd23b3af1f2eeb5be03 to your computer and use it in GitHub Desktop.
Save abiusx/15ebb19ce5291bd23b3af1f2eeb5be03 to your computer and use it in GitHub Desktop.
Type Checked PHP (methods, functions are not yet supported)
<?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