PHPParser.php

<?php

namespace Taeluf\PHTML;

/**
 * Parses .php files into:
 *  1. A string with placeholders for the PHP code
 *  2. An array of PHP code, identified by their placeholders
 * 
 */
class PHPParser {
    
    /**
     * The source HTML + PHP code
     *
     * @var string
     */
    protected string $src;
    /**
     * The parsed pieces of code in the format:
     *  [
     *      'html' => '<div>A string of code with php placeholders like <b>phpadsfwefhaksldfjaskdlfjaskldfjasldfkphp</b> </div> and trailing text...',
     *      'php'  => [
     *          'phpadsfwefhaksldfjaskdlfjaskldfjasldfkphp' => '<?="Something echod";?>', 
     *          'phpid2'=>'<?php // another code block/?>'
     *      ]
     *  ]
     * The array is simply (object) cast
     */
    protected object $pieces;
    
    /**
     * Create a new PHP parser instance from a string
     *
     * @param  mixed $htmlPHPString - a block of HTML + PHP code. PHP code will be inside open (<?php or <?=) and close (?>) tags
     * @return void
     */
    public function __construct(string $htmlPHPString){
        $this->src = $htmlPHPString;
        $this->pieces = $this->separatePHP();
    }

        
    /**
     * Return the parsed pieces. See doc for protected $pieces
     *
     * @return object
     */
    public function pieces(): object{
        return $this->pieces;
    }
    /** Separate the source code into it's pieces. See the protected $pieces docs
     *
     * @return object just an array cast to object
     */
    protected function separatePHP(): object{
        $tokens = $this->tokens();
        $str = '';
        $inPHP = false;
        $phpCode = '';
        $phpList = [];
        foreach ($tokens as $index => $token){
            $token = (object)$token;
            if ($token->type=='T_OPEN_TAG'
                ||$token->type=='T_OPEN_TAG_WITH_ECHO'){
                $inPHP = true;
            }
            if (!$inPHP){
                $str .= $token->code;
            }
            if ($inPHP){
                $phpCode .= $token->code;
            }
            if ($token->type=='T_CLOSE_TAG'){
                $id = 'php'.$this->randomAlpha().'php';
                $phpList[$id] = $phpCode;
                $phpCode = '';
                $str .= $id;
                $inPHP = false;
            }
        }
        return (object)[
            'php'=>$phpList,
            'html'=>$str
        ];

    }
        
    /**
     * Generates a random string of characters a-z, all lowercase & returns them
     *  this is used for the PHP placeholders
     * 
     * @param int $length
     * @return string
     */
    protected function randomAlpha($length = 26): string {
        return static::getRandomAlpha($length);
    }
    /**
     * Generates a random string of characters a-z, all lowercase & returns them
     *  this is used for the PHP placeholders
     * 
     * @param int $length
     * @return string
     */
    static public function getRandomAlpha($length = 26): string {
        $characters = 'abcdefghijklmnopqrstuvwxyz';
        $charactersLength = strlen($characters);
        $randomString = '';
        for ($i = 0; $i < $length; $i++) {
            $randomString .= $characters[rand(0, $charactersLength - 1)];
        }
        return $randomString;
    }
    
        
    /**
     * Tokenize the src code into a slightly better format than token_get_all
     *
     * @return array of tokens
     */
    protected function tokens(): array{
        $content = $this->src;
        $tokens = token_get_all($content);
        $niceTokens = [];

        $delLine = null;
        foreach ($tokens as $index=>$token){
            $tok = [];
            if (!is_array($token)){
                $lastTok = array_slice($niceTokens,-1)[0];
                $tok['type'] = "UNNAMED_TOKEN";
                $tok['code'] = $token;
                $tok['line'] = $lastTok['line'];
            } else {
                $tok['type'] = token_name($token[0]);
                $tok['code'] = $token[1];
                $tok['line'] = $token[2];
            }
            // This is old code for an idea I had
            // if ($tok['type']=='T_STRING'&&$tok['code']=='PHPPlus'){
                // $next = $tokens[$index+1];
                // if (is_array($next)&&$next[1]=='::'){
                    // $delLine = $tok['line'];
                    // echo 'del line is '.$delLine."\n\n";
                // }
            // }
            $niceTokens[] = $tok;
        }
        foreach ($niceTokens as $index=>$token){
            if ($token['line']===$delLine){
                unset($niceTokens[$index]);
            }
        }
        return $niceTokens;
    }

}