<?php
namespace Taeluf\PHTML;
class Compiler {
/**
* An array of code to output.
* Likely contains placeholder which will be replaced.
* May contain objects which implement __toString
*/
protected $code = [];
/**
* The content of a PHP file for compilation
*/
protected $src;
/**
* An array of placeholder code with codeId => [prependedCode, code, appendedCode]... there can be any number of entries for each codeId
* Code may be string or an object which implements __toString
* codeIds are either sha sums (alpha-numeric, i think) or randmoized alpha
*/
protected $placeholder = [];
/**
* The parsed source code, with the PHP code replaced by placeholders
*/
protected $htmlSource;
public function __construct(){
}
/**
* Replaces inline PHP code with placeholder, indexes the placeholder, and returns the modified code
*
* @param mixed $srcCode - The source code
*
* @return string - source code with all PHP replaced by codeIds
*/
public function cleanSource($srcCode): string{
$parser = new PHPParser($srcCode);
$parsed = $parser->pieces();
foreach ($parsed->php as $id=>$code){
$this->placeholder[$id] = [$code];
}
return $parsed->html;
}
/**
* 1. Generates an id
* 2. indexes the passed-in-code with that id
* 3. Returns the id.
*
* @param mixed $phpCodeWithOpenCloseTags - Code, as it would be found in a PHP file. AKA PHP code MUST include open/close tags
* @return string the codeId. Either a random alpha-string OR an sha_sum, which I think is alpha-numeric
*/
public function placeholderFor($phpCodeWithOpenCloseTags): string{
$code = $phpCodeWithOpenCloseTags;
$id = $this->freshId();
$this->placeholder[$id] = [$code];
return $id;
}
/**
* Appends code to the output-to-be
*
* @param mixed $code - Code to append. PHP code must be wrapped in open/close tags
* @return void
*/
public function appendCode($code){
$this->code[] = $code;
}
/**
* Prepends code to the output-to-be
*
* @param mixed $code - Code to prepend. PHP code must be wrapped in open/close tags
* @return void
*/
public function prependCode($code){
// $this->code[] = $code;
array_unshift($this->code,$code);
}
/**
* Prepend code immediately prior to the given placeholder
*
* @param mixed $placeholder - a placeholder from placeholderFor()
* @param mixed $code - a block of code. PHP must be wrapped in open/close tags
* @return void
*/
public function placeholderPrepend($placeholder,$code){
array_unshift($this->placeholder[$placeholder],$code);
}
/**
* Append code immediately after the given placeholder
*
* @param mixed $placeholder - a placeholder from placeholderFor()
* @param mixed $code - a block of code. PHP must be wrapped in open/close tags
* @return void
*/
public function placeholderAppend($placeholder,$code){
$this->placeholder[$placeholder][] = $code;
}
/**
* Compile the code into a string & return it.
* output() can be called several times as it does NOT affect the state of the compiler.
*
* @return string
*/
public function output(): string{
// print_r($this->code);
// // echo $this->code[0]->;
// exit;
// print_r($this->placeholder);
$code = implode("\n",$this->code);
// return $code;
$ph = [];
foreach ($this->placeholder as $id=>$codeArray){
$ph[$id] = implode('',$codeArray);
}
$last = $code;
while($last != $code = str_replace(array_keys($ph),$ph,$code))$last=$code;
return $code;
}
/**
* Writes the compiled output to the given file
*
* @param string $file - an absolute filepath
* @param $chmodTo - REMOVED DOES NOTHING
*
* 0644 or whatever. If null, chmod will not be run.
* See https://www.php.net/manual/en/function.chmod.php
* Permissions are as follows:
* Value Permission Level
* 200 Owner Write
* 400 Owner Read
* 100 Owner Execute
* 40 Group Read
* 20 Group Write
* 10 Group Execute
* 4 Global Read
* 2 Global Write
* 1 Global Execute
* @return boolean true if file_put_contents succeeds. False otherwise.
*/
public function writeTo(string $file, $chmodTo=null): bool {
$output = $this->output();
if (!is_dir(dirname($file))){
mkdir(dirname($file),0771,true);
// chmod(dirname($file),0770);
}
$didPut = file_put_contents($file,$output);
if ($chmodTo!==null){
// chmod($file,$chmodTo);
}
if ($didPut===false)return false;
else return true;
}
/**
* Get an absolute file path which can be included to execute the given code
*
* 1. $codeId = sha1($code)
* 2. file_put_contents("$compileDir/$codeId.php", $code)
* 3. return the path of the new file
*
* - Will create the directory (non-recursive) if not exists
*
* @param mixed $compileDir - the directory in which the file should be written
* @param mixed $code - the block of code. PHP code must be wrapped in open/close tags to be executed
* @return string an absolute file path to a php file
*/
public function fileForCode($compileDir,$code): string{
// $codeId = $this->freshId(30);
$codeId = sha1($code);
$file = $compileDir.'/'.$codeId.'.php';
if (!file_exists($compileDir))mkdir($compileDir,0770,true);
file_put_contents($file,$code);
return $file;
}
/**
* Generate a random string of lowercase letters
*
* @param mixed $length The desired length of the random string. Default is 26 to avoid any clashing
* @return string the random string
*/
protected function freshId($length = 26): string {
$characters = 'abcdefghijklmnopqrstuvwxyz';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
/**
* Get the code for the given code id.
*
* Placeholder code is stored as an array to enable the placeholderPrepend|Append functions, so I make it available as an array if you want.
*
*
* @param string $codeId - the id generated by placeholderFor()
* @param bool $asArray - TRUE to get the code as it's array parts. FALSE to implode the array (no newlines) & return that
* @return mixed an array of code pieces that make up this codeid or a string of the code
*/
public function codeForId(string $codeId,bool $asArray=false){
$codeArr = $this->placeholder[$codeId];
if ($asArray)return $codeArr;
else return implode('',$codeArr);
}
}