Skip to content

Instantly share code, notes, and snippets.

@cynthia
Created July 8, 2017 18:37
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 cynthia/b59bc7439a8d7c289ee3c71409bcad24 to your computer and use it in GitHub Desktop.
Save cynthia/b59bc7439a8d7c289ee3c71409bcad24 to your computer and use it in GitHub Desktop.
Weird science experiment that makes every non-existent class in the current PHP execution context ORM backed with Redbean.
<?php
/**
* UPO : Unidentified PHP Object (미확인 PHP 객체)
*
* Here be dragons. This abuses every odd PHP language feature known to man.
* which means it is highly experimental, and should *never* be used in a
* production environment. Failing to do so may expose your application to
* numerous security exploits, and the author takes no liability for damages
* caused by ignoring this warning.
*
* @file UPO.php
* @desc Main class for UPO
* @author Sangwhan Moon <sangwhan@iki.fi>
* @license 3-clause BSD
*/
require_once("rb.php");
error_reporting(E_ALL);
//////////////////////////////////////////////////////////////////////////////
// Constants
//////////////////////////////////////////////////////////////////////////////
define("GT", "Gt");
define("ZZSEP", DIRECTORY_SEPARATOR); // DIRECTORY_SEPARATOR shorthand.
define("ZZIDF", "zn7q"); // Model prefix.
define("ZZCSP", "z9qx"); // Model internal token.
define("ZZMEM", "_"); // Model camel case replacement.
define("ZZMOD", "Model_" . ucfirst(ZZIDF)); // Redbean model name prefix.
// Shorthand for generated class path.
define("ZZGCP", dirname(__FILE__) . ZZSEP . "generated" . ZZSEP);
define("C_OPGT", "get");
define("C_OPST", "set");
define("C_OPFR", "from");
define("C_OPAL", "all");
define("T_OPRD", 0);
define("T_OPWR", 1);
define("T_OPID", 2);
define("T_DECL", 0);
define("T_SETP", 1);
define("T_GETP", 2);
define("T_IDXP", 3);
define("T_CINC", 4);
define("T_CDEC", 5);
define("T_CNIL", 6);
//////////////////////////////////////////////////////////////////////////////
// Config
//////////////////////////////////////////////////////////////////////////////
define("DB_STAGING", "sqlite:" . dirname(__FILE__) . ZZSEP . "../db/staging.db");
define("DB_STAGING_U", "staging");
define("DB_STAGING_P", "pepsiman");
define("M_DBG", TRUE);
define("MODE_LAX", FALSE);
define("MODE_AGGRESSIVELY_STORE", FALSE);
define("MODE_WRITE_CLASSES", TRUE);
define("MODE_PURGE_ON_INIT", TRUE);
//////////////////////////////////////////////////////////////////////////////
// Autoloader and JIT class generation
//////////////////////////////////////////////////////////////////////////////
spl_autoload_register("UPO::__autoload");
//////////////////////////////////////////////////////////////////////////////
// Bootstrap
//////////////////////////////////////////////////////////////////////////////
\R::setup(DB_STAGING, DB_STAGING_U, DB_STAGING_P);
if (MODE_PURGE_ON_INIT === TRUE) {
UPO::__purge_generated();
}
class OperationNotPermittedException extends \Exception {}
class ReferenceCountMismatchException extends \Exception {}
class ObjectNotReadyException extends \Exception {}
class ObjectNotFoundException extends \Exception {}
class FromWithMultipleNotAllowedException extends \Exception {}
class ForbiddenObjectNameException extends \Exception {}
class NotImplementedException extends \Exception {}
//////////////////////////////////////////////////////////////////////////////
// Implementation
//////////////////////////////////////////////////////////////////////////////
class UPO
{
private static $reference_count_ = 0;
private static $traces_ = array();
private static $purged_ = FALSE;
private $instance_ = NULL;
private $bean_id_ = NULL;
private $cached_name_ = NULL;
////////////////////////////////////////////////////////////////////////////
// Buit-in magic methods (from PHP)
////////////////////////////////////////////////////////////////////////////
function __construct()
{
$a = func_get_args();
$i = func_num_args();
if ($i == 1) {
$this->instance_ = \R::find(self::__tclazz(), $i);
}
$this->root_name_ = $this->__root_type();
self::$reference_count_++;
self::__trace(T_CINC);
}
function __destruct() {
self::$reference_count_--;
self::__trace(T_CDEC);
if ($this->instance_ !== NULL) {
$this->instance_->__type = self::__clazz();
$this->bean_id_ = \R::store($this->instance_);
}
if (self::$reference_count_ == 0) {
self::__trace(T_CNIL);
\R::close();
}
}
public function __call($name, $arguments)
{
// Don't handle __call coming from Redbean. Failing to do so wreaks havoc.
if ($name == "loadBean") return;
$op = explode(ZZMEM,
strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name)),
2);
switch ($op[0])
{
case C_OPGT:
{
if (!$this->instance_ && !MODE_LAX) {
throw new ObjectNotReadyException();
}
else {
// Warning intentionally supressed, as we use the hard FALSE return
// value to determine if what was stored was a array or not. Ugly,
// but works.
@$temp = unserialize($this->instance_->$op[1]);
// IDs (Redbean identifiers) are not object properties, hence we do
// not trace them.
if ($op[1] == "id") {
$this->bean_id_ = \R::store($this->instance_);
return $this->bean_id_;
}
self::__trace(T_GETP, self::__tclazz(), $op[1]);
if ($temp !== FALSE)
return $temp;
return $this->instance_->$op[1];
}
break;
}
case C_OPST:
{
if (!$this->instance_) {
$this->instance_ = \R::dispense(self::__tclazz($this->root_name_));
}
if (count($arguments) == 1) {
if (is_array($arguments[0])) {
self::__trace(T_SETP, self::__tclazz(), $op[1]);
$this->instance_->$op[1] = serialize($arguments[0]);
}
else {
self::__trace(T_SETP, self::__tclazz(), $op[1]);
$this->instance_->$op[1] = $arguments[0];
}
}
if (count($arguments) > 1) {
self::__trace(T_SETP, self::__tclazz(), $op[1]);
$this->instance_->$op[1] = serialize($arguments);
}
if (MODE_AGGRESSIVELY_STORE) {
if ($this->instance_) {
$this->bean_id_ = \R::store($this->instance_);
}
}
break;
}
case C_OPFR:
{
if (count($arguments) == 1) {
self::__trace(T_IDXP, self::__tclazz(), $op[1]);
$type = self::__clazz();
// echo($op[1] . "\n" . $arguments[0] . "\n" . $type . "\n");
$this->instance_ = $op[1] == "id" ?
R::load(self::__tclazz($this->root_name_), $arguments[0]) :
$this->instance_ = R::findOne(self::__tclazz($this->root_name_),
sprintf("%s = ? AND __type = ?", $op[1]),
[$arguments[0], $type]);
print_r($this->instance_);
}
if (count($arguments) > 1) {
// FIXME: Should Student->fromName("John", "Jane") return a Student
// instance with the name either John or Jane? Not sure. Don't do
// anything for now.
throw new FromWithMultipleNotAllowedException();
}
break;
}
case C_OPAL:
{
throw new NotImplementedException();
}
default:
{
if (!MODE_LAX)
throw new OperationNotPermittedException();
}
}
}
public static function __callStatic($name, $arguments)
{
$op = explode(ZZMEM,
strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name)),
2);
switch ($op[0])
{
case C_OPST:
case C_OPGT:
{
throw new NotImplementedException();
break;
}
case C_OPFR:
{
if (count($arguments) == 1) {
$class_name = self::__clazz();
$ret = new $class_name;
$ret->$name($arguments[0]);
return $ret;
}
break;
}
case C_OPAL:
{
throw new NotImplementedException();
}
default:
{
if (!MODE_LAX)
throw new OperationNotPermittedException();
}
}
}
////////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////////
public static function __autoload($class_name) {
if (substr($class_name, 0, 2) == GT) {
// && substr($class_name, 0, 10) != ZZMOD) {
if (file_exists($class_name . ".php")) {
require_once($class_name . ".php");
}
else if (file_exists(ZZGCP . $class_name . ".upo.php")) {
require_once(ZZGCP . $class_name . ".upo.php");
}
else {
UPO::__declare_clazz($class_name);
}
}
}
public static function __declare_clazz()
{
$a = func_get_args();
$i = func_num_args();
if ($i == 1) {
self::__trace(T_DECL, $a[0], "UPO");
self::__declare_sclazz($a[0], "UPO");
}
// NOTE: This code is not used internally.
else if ($i > 1) {
$parent_clazz = "UPO";
for ($j = $i - 1; $j >= 0; $j--) {
self::__trace(T_DECL, $a[$j], $parent_clazz);
self::__declare_sclazz($a[$j], $parent_clazz);
$parent_clazz = $a[$j];
}
}
else {
// This only happens if someone calls __declare_clazz with no args.
if (!MODE_LAX)
throw new OperationNotPermittedException();
}
}
public static function __declare_sclazz($class_name, $parent_name)
{
if (!preg_match("/^[a-zA-Z0-9_]+$/", $class_name . $parent_name)) {
throw new ForbiddenObjectNameException();
}
$generated = "class " . $class_name . " extends " . $parent_name .
"\n{\n\t\n}\n";
eval($generated);
}
public function flush() {
if ($this->instance_) {
$this->bean_id_ = \R::store($this->instance_);
}
}
public function __instance_bean()
{
return $this->instance_;
}
public static function __purge_generated()
{
$generated_classes = scandir(ZZGCP);
foreach ($generated_classes as $generated_class) {
if (substr($generated_class, -8) == ".upo.php") {
unlink(ZZGCP . $generated_class);
}
}
self::$purged_ = TRUE;
}
private static function __append_trace($type, $clazz, $func)
{
if (!isset(self::$traces_[$clazz])) {
self::$traces_[$clazz] = array();
}
if (!isset(self::$traces_[$clazz][$func])) {
self::$traces_[$clazz][$func] =
array(T_OPRD => 0, T_OPWR => 0, T_OPID => 0);
}
self::$traces_[$clazz][$func][$type]++;
}
private static function __trace()
{
$a = func_get_args();
switch ($a[0])
{
case T_DECL:
{
if (M_DBG)
printf("[T_DECL]\t class %s extends %s dynamically declared.\n",
$a[1], $a[2]);
return;
}
case T_SETP:
{
if (M_DBG)
printf("[T_SETP]\t %s property %s set.\n", $a[1], $a[2]);
self::__append_trace(T_OPWR, $a[1], $a[2]);
return;
}
case T_GETP:
{
if (M_DBG)
printf("[T_GETP]\t %s property %s get.\n", $a[1], $a[2]);
self::__append_trace(T_OPRD, $a[1], $a[2]);
return;
}
case T_IDXP:
{
if (M_DBG)
printf("[T_IDXP]\t %s property %s used as index.\n", $a[1], $a[2]);
self::__append_trace(T_OPID, $a[1], $a[2]);
return;
}
case T_CINC:
{
if (M_DBG)
printf("[T_CINC]\t Reference count is %d. Last object was %s.\n",
self::$reference_count_, self::__clazz());
return;
}
case T_CDEC:
{
if (M_DBG)
printf("[T_CDEC]\t Reference count is %d. Last object was %s.\n",
self::$reference_count_, self::__clazz());
return;
}
case T_CNIL:
{
if (M_DBG)
printf("[T_CNIL]\t Reference clean! Attempting DB shutdown.\n");
foreach (self::$traces_ as $internal_class_name => $trace) {
$class_name = self::__rclazz($internal_class_name, ZZCSP);
if (MODE_WRITE_CLASSES) {
$pclass = get_parent_class($class_name);
$gen = "class " . $class_name . " extends " . $pclass . "\n{\n";
$class_file = fopen(ZZGCP . $class_name . ".upo.php", "w");
fwrite($class_file, "<?php\n\n// Class generated by UPO.\n\n");
fwrite($class_file, $gen);
}
foreach ($trace as $internal_member => $counter) {
$member = self::__rclazz($internal_member, ZZMEM);
if (M_DBG)
printf("[T_STAT]\t %s.%s read %d, written %d, index %d times\n",
$class_name, $member, $counter[T_OPRD],
$counter[T_OPWR], $counter[T_OPID]);
if ($pclass != "UPO") {
if ($counter[T_OPRD] > 0)
self::__append_trace(T_OPRD, $pclass, $member);
if ($counter[T_OPWR] > 0)
self::__append_trace(T_OPWR, $pclass, $member);
if ($counter[T_OPID] > 0)
self::__append_trace(T_OPID, $pclass, $member);
}
if (MODE_WRITE_CLASSES) {
$fl = "";
$fl .= $counter[T_OPRD] > 0 ? "R" : "";
$fl .= $counter[T_OPWR] > 0 ? "W" : "";
$fl .= $counter[T_OPID] > 0 ? "I" : "";
fwrite($class_file, "\t// @hint " . $fl . " " . $member . "\n");
}
}
if (MODE_WRITE_CLASSES) {
fwrite($class_file, "}\n\n?>");
fclose($class_file);
}
}
return;
}
}
}
private static function __clazz()
{
return get_called_class();
}
private static function __pclazz()
{
return get_parent_class(self);
}
private static function __tclazz($clazz = NULL)
{
return (ZZIDF . strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . ZZCSP .
"$2", !$clazz ? get_called_class() : $clazz)));
}
private static function __rclazz($class_name, $token)
{
// WORKAROUND: str_replace() is broken, so we do a domain specific
// implementation here.
if (strpos($class_name, ZZIDF) !== FALSE) {
$class_name = substr($class_name, strlen(ZZIDF));
}
$camels = explode($token, $class_name);
$buffer = "";
foreach ($camels as $i => $camel) {
$buffer .= ($i == 0 && $token == ZZMEM) ? $camel : ucfirst($camel);
}
return $buffer;
}
private function __root_type()
{
$last_class = self::__clazz();
if (get_parent_class($last_class) != "UPO") {
for (;;) {
if (get_parent_class($last_class) == "UPO") {
break;
} else {
$last_class = get_parent_class($last_class);
}
}
}
return $last_class;
}
private function __ready()
{
return $this->instance_ === NULL;
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment