Compiler.php

<?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);
    }
}