Source of file MetaCompiler.php
Size: 6,868 Bytes - Last Modified: 2021-12-23T10:20:54+00:00
/var/www/docs.ssmods.com/process/src/src/Ralph/MetaCompiler.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 | <?php namespace Symbiote\Ralph; use SS_ClassLoader; use Debug; use Config; use ClassInfo; require_once(dirname(__FILE__).'/../ss4_compat.php'); class MetaCompiler { public function postConstructCall() { singleton('Symbiote\\Ralph\\Ralph')->constructorStore($this); } public function preFunctionCall() { $timeDifference = microtime(true); } public function postFunctionCall() { $timeDifference = microtime(true) - $timeDifference; singleton('Symbiote\\Ralph\\Ralph')->profilerStore($this, __FUNCTION__, $timeDifference); } public function process() { $ralph = singleton('Symbiote\\Ralph\\Ralph'); $dumpToFiles = $ralph->getDumpToFile(); $codeGenerateFolder = BASE_PATH.'/ralph/src_generated/'; $classesToInstrument = array(); $classesToSkip = array(); $classes = $ralph->getClasses(); // Add all subclasses of the specified object // ie. DataList will also instrument ManyManyList, HasManyList, etc. foreach ($classes as $class => $f) { $subClasses = ClassInfo::subclassesFor($class); foreach ($subClasses as $subClass) { if (isset($classesToSkip[$subClass])) { continue; } $abstract = new \ReflectionClass($subClass); if ($abstract->isAbstract()) { continue; } $originalSubClass = $subClass; $config = Config::inst()->get('Injector', $subClass); if (isset($config['class'])) { // Use Injected class instead, one use case is so it uses the 'datachange-tracker' modules // TrackedManyManyList, instead of ManyManyList $subClass = $config['class']; // Prevent injected class from being added twice $classesToSkip[$subClass] = true; unset($classes[$subClass]); } $classesToInstrument[$originalSubClass] = array( 'class' => $subClass, 'functions' => $f, ); } } if ($dumpToFiles) { $files = glob($codeGenerateFolder.'*.php'); foreach ($files as $file) { @unlink($file); } } foreach ($classesToInstrument as $originalClass => $data) { $class = $data['class']; $functionsToInstrument = $data['functions']; $code = $this->generateClassCode($class, $functionsToInstrument); if ($dumpToFiles) { // Used to debug custom instrumented code $classSanitised = str_replace('\\', '_', $class); $filepath = $codeGenerateFolder.$classSanitised.'.php'; file_put_contents($filepath, "<?php\n".$code); } if (class_exists('\ParseError')) { try { eval($code); } catch (\ParseError $e) { echo 'A parse error occurred with Ralph\'s instrumentation method.'; echo $e->getMessage().'<br/>'; echo 'Line: '.$e->getLine().'<br/>'; echo '<pre>'.print_r($code, true).'</pre>'; exit; } } else { eval($code); } } // Update Injector to use new class foreach ($classesToInstrument as $originalClass => $data) { $class = $data['class']; $classNameWithoutNamespace = str_replace('\\', '_', $class); $ralph->useCustomClass($originalClass, 'Symbiote\\Ralph\\Generated\\'.$classNameWithoutNamespace); } } public function generateClassCode($class, array $functions) { $thisClass = get_class(); $defaultPreFunctionCall = $this->getMethodBody($thisClass, 'preFunctionCall'); $defaultPostFunctionCall = $this->getMethodBody($thisClass, 'postFunctionCall'); $indent = "\t"; $indent2x = $indent.$indent; $classSanitised = str_replace('\\', '_', $class); $newClass = ''; $newClass .= "\n"; $newClass .= '// Warning: This file is auto-generated by MetaCompiler in the Ralph module.'."\n\n"; $newClass .= 'namespace Symbiote\Ralph\Generated;'."\n"; $newClass .= "\n"; $newClass .= 'class '.$classSanitised.' extends \\'.$class.' {'."\n"; foreach ($functions as $function => $info) { $preFunctionCall = $defaultPreFunctionCall; $postFunctionCall = $defaultPostFunctionCall; if (is_numeric($function)) { $function = $info; } else { $preFunctionCall = $info[0]; if ($preFunctionCall) { $preFunctionCall = $this->getMethodBody($thisClass, $preFunctionCall); } else { $preFunctionCall = ''; } $postFunctionCall = $info[1]; if ($postFunctionCall) { $postFunctionCall = $this->getMethodBody($thisClass, $postFunctionCall); } else { $postFunctionCall = ''; } } $reflect = new \ReflectionMethod($class, $function); $modifiers = \Reflection::getModifierNames($reflect->getModifiers()); $modifiers = implode(' ',$modifiers); $newClass .= $indent.$modifiers.' function '.$function.'('; $parameters = $reflect->getParameters(); foreach ($parameters as $i => $field) { if ($i > 0) { $newClass.=','; } // Get class hint $typeHint = ''; $fieldClassHint = $field->getClass(); if ($fieldClassHint && $fieldClassHint->name) { $typeHint = '\\'.$fieldClassHint->name.' '; } // NOTE(Jake): Store value like this so I can detect 'null' values // isset() returns false on 'null' $value = array(); try { if ($field->isDefaultValueAvailable()) { $value[] = $field->getDefaultValue(); } /*else if ($field->isDefaultValueConstant()) { $value[] = $field->getDefaultValueConstantName(); }*/ } catch (ReflectionException $e) { // no-op } $newClass .= $typeHint.'$'.$field->name; if ($value) { $value = $value[0]; if ($value === null) { $newClass .= ' = null'; } else if (is_array($value)) { $newClass .= ' = array('; $isFirst = true; foreach ($value as $i => $subval) { if ($isFirst === false) { $newClass .= ','; } $newClass .= $subval; $isFirst = false; } $newClass .= ')'; } else { $newClass .= ' = '.$value; } } } $newClass .= ')'; $newClass .= "{\n"; $newClass .= $indent2x.$preFunctionCall."\n"; // todo(Jake): add params $newClass .= $indent2x.'$___result = parent::'.$function.'('; foreach ($parameters as $i => $field) { if ($i > 0) { $newClass.=','; } $newClass .= '$'.$field->name; } $newClass .= ");\n"; $newClass .= $indent2x.$postFunctionCall."\n"; $newClass .= $indent2x.'return $___result;'."\n"; $newClass .= $indent."}\n\n"; } $newClass .= "}\n"; return $newClass; } /** * Get the PHP code used inside a method as text. * * @return string */ public function getMethodBody($class, $function) { $func = new \ReflectionMethod($class, $function); $filename = $func->getFileName(); $start_line = $func->getStartLine() - 1; $end_line = $func->getEndLine(); $length = $end_line - $start_line; $source = file($filename); $body = implode('', array_slice($source, $start_line, $length)); $startPos = strpos($body, '{'); $endPos = strrpos($body, '}'); $body = trim(substr($body, $startPos + 1, ($endPos-$startPos) - 1)); return $body; } } |