setPath($path); } if (! is_null($this->cachePath)) { $contents = $this->compileString($this->files->get($this->getPath())); $this->files->put($this->getCompiledPath($this->getPath()), $contents); } } /** * Get the path currently being compiled. * * @return string */ public function getPath() { return $this->path; } /** * Set the path currently being compiled. * * @param string $path * @return void */ public function setPath($path) { $this->path = $path; } /** * Compile the given Blade template contents. * * @param string $value * @return string */ public function compileString($value) { if (strpos($value, '@verbatim') !== false) { $value = $this->storeVerbatimBlocks($value); } $this->footer = []; if (strpos($value, '@php') !== false) { $value = $this->storePhpBlocks($value); } $result = ''; // Here we will loop through all of the tokens returned by the Zend lexer and // parse each one into the corresponding valid PHP. We will then have this // template as the correctly rendered PHP that can be rendered natively. foreach (token_get_all($value) as $token) { $result .= is_array($token) ? $this->parseToken($token) : $token; } if (! empty($this->rawBlocks)) { $result = $this->restoreRawContent($result); } // If there are any footer lines that need to get added to a template we will // add them here at the end of the template. This gets used mainly for the // template inheritance via the extends keyword that should be appended. if (count($this->footer) > 0) { $result = $this->addFooters($result); } return $result; } /** * Store the verbatim blocks and replace them with a temporary placeholder. * * @param string $value * @return string */ protected function storeVerbatimBlocks($value) { return preg_replace_callback('/(?storeRawBlock($matches[1]); }, $value); } /** * Store the PHP blocks and replace them with a temporary placeholder. * * @param string $value * @return string */ protected function storePhpBlocks($value) { return preg_replace_callback('/(?storeRawBlock(""); }, $value); } /** * Store a raw block and return a unique raw placeholder. * * @param string $value * @return string */ protected function storeRawBlock($value) { return $this->getRawPlaceholder( array_push($this->rawBlocks, $value) - 1 ); } /** * Replace the raw placeholders with the original code stored in the raw blocks. * * @param string $result * @return string */ protected function restoreRawContent($result) { $result = preg_replace_callback('/'.$this->getRawPlaceholder('(\d+)').'/', function ($matches) { return $this->rawBlocks[$matches[1]]; }, $result); $this->rawBlocks = []; return $result; } /** * Get a placeholder to temporary mark the position of raw blocks. * * @param int|string $replace * @return string */ protected function getRawPlaceholder($replace) { return str_replace('#', $replace, '@__raw_block_#__@'); } /** * Add the stored footers onto the given content. * * @param string $result * @return string */ protected function addFooters($result) { return ltrim($result, PHP_EOL) .PHP_EOL.implode(PHP_EOL, array_reverse($this->footer)); } /** * Parse the tokens from the template. * * @param array $token * @return string */ protected function parseToken($token) { list($id, $content) = $token; if ($id == T_INLINE_HTML) { foreach ($this->compilers as $type) { $content = $this->{"compile{$type}"}($content); } } return $content; } /** * Execute the user defined extensions. * * @param string $value * @return string */ protected function compileExtensions($value) { foreach ($this->extensions as $compiler) { $value = call_user_func($compiler, $value, $this); } return $value; } /** * Compile Blade statements that start with "@". * * @param string $value * @return string */ protected function compileStatements($value) { return preg_replace_callback( '/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', function ($match) { return $this->compileStatement($match); }, $value ); } /** * Compile a single Blade @ statement. * * @param array $match * @return string */ protected function compileStatement($match) { if (Str::contains($match[1], '@')) { $match[0] = isset($match[3]) ? $match[1].$match[3] : $match[1]; } elseif (isset($this->customDirectives[$match[1]])) { $match[0] = $this->callCustomDirective($match[1], Arr::get($match, 3)); } elseif (method_exists($this, $method = 'compile'.ucfirst($match[1]))) { $match[0] = $this->$method(Arr::get($match, 3)); } return isset($match[3]) ? $match[0] : $match[0].$match[2]; } /** * Call the given directive with the given value. * * @param string $name * @param string|null $value * @return string */ protected function callCustomDirective($name, $value) { if (Str::startsWith($value, '(') && Str::endsWith($value, ')')) { $value = Str::substr($value, 1, -1); } return call_user_func($this->customDirectives[$name], trim($value)); } /** * Strip the parentheses from the given expression. * * @param string $expression * @return string */ public function stripParentheses($expression) { if (Str::startsWith($expression, '(')) { $expression = substr($expression, 1, -1); } return $expression; } /** * Register a custom Blade compiler. * * @param callable $compiler * @return void */ public function extend(callable $compiler) { $this->extensions[] = $compiler; } /** * Get the extensions used by the compiler. * * @return array */ public function getExtensions() { return $this->extensions; } /** * Register an "if" statement directive. * * @param string $name * @param callable $callback * @return void */ public function if($name, callable $callback) { $this->conditions[$name] = $callback; $this->directive($name, function ($expression) use ($name) { return $expression !== '' ? "" : ""; }); $this->directive('else'.$name, function ($expression) use ($name) { return $expression !== '' ? "" : ""; }); $this->directive('end'.$name, function () { return ''; }); } /** * Check the result of a condition. * * @param string $name * @param array $parameters * @return bool */ public function check($name, ...$parameters) { return call_user_func($this->conditions[$name], ...$parameters); } /** * Register a component alias directive. * * @param string $path * @param string $alias * @return void */ public function component($path, $alias = null) { $alias = $alias ?: array_last(explode('.', $path)); $this->directive($alias, function ($expression) use ($path) { return $expression ? "startComponent('{$path}', {$expression}); ?>" : "startComponent('{$path}'); ?>"; }); $this->directive('end'.$alias, function ($expression) { return 'renderComponent(); ?>'; }); } /** * Register an include alias directive. * * @param string $path * @param string $alias * @return void */ public function include($path, $alias = null) { $alias = $alias ?: array_last(explode('.', $path)); $this->directive($alias, function ($expression) use ($path) { $expression = $this->stripParentheses($expression) ?: '[]'; return "make('{$path}', {$expression}, array_except(get_defined_vars(), array('__data', '__path')))->render(); ?>"; }); } /** * Register a handler for custom directives. * * @param string $name * @param callable $handler * @return void */ public function directive($name, callable $handler) { $this->customDirectives[$name] = $handler; } /** * Get the list of custom directives. * * @return array */ public function getCustomDirectives() { return $this->customDirectives; } /** * Set the echo format to be used by the compiler. * * @param string $format * @return void */ public function setEchoFormat($format) { $this->echoFormat = $format; } /** * Set the "echo" format to double encode entities. * * @return void */ public function withDoubleEncoding() { $this->setEchoFormat('e(%s, true)'); } /** * Set the "echo" format to not double encode entities. * * @return void */ public function withoutDoubleEncoding() { $this->setEchoFormat('e(%s, false)'); } }