Skip to content

[5.3] Implement blade macros feature #16583

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions src/Illuminate/View/Compilers/BladeCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@

namespace Illuminate\View\Compilers;

use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\View\ViewFinderInterface;

class BladeCompiler extends Compiler implements CompilerInterface
{
/**
* @var ViewFinderInterface
*/
protected $viewFinder;

/**
* All of the registered extensions.
*
Expand Down Expand Up @@ -37,6 +44,7 @@ class BladeCompiler extends Compiler implements CompilerInterface
*/
protected $compilers = [
'Extensions',
'Macros',
'Statements',
'Comments',
'Echos',
Expand Down Expand Up @@ -98,6 +106,23 @@ class BladeCompiler extends Compiler implements CompilerInterface
*/
protected $forelseCounter = 0;

/**
* Create a BladeCompiler instance.
*
*
* @param Filesystem $files
* @param string $cachePath
* @param string ViewFinderInterface $viewFinder
*
* @return void
*/
public function __construct(Filesystem $files, $cachePath, ViewFinderInterface $viewFinder)
{
parent::__construct($files, $cachePath);

$this->viewFinder = $viewFinder;
}

/**
* Compile the view at the given path.
*
Expand Down Expand Up @@ -242,6 +267,46 @@ protected function compileExtensions($value)
return $value;
}

/**
* Compile Macros
* s they are not treated as View instances they should be compiled
* before the statements are compiled.
*
* @param string $value
* @return string
*/
protected function compileMacros($value)
{
$pattern = '/\B@(macro)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x';

while (preg_match($pattern, $value, $matches)) {
$expression = $this->stripParentheses($matches[3]);

if (Str::endsWith($expression, ')')) {
$expression = substr($expression, 0, -1);
}

// We replace each occurrence of the @macro
// with the contents of its file, and wrap the
// imported content around a self-invokable function to satisfy the macro scope,
// So file changes are not detected, there is a need to clear compiled views on
// development environments.
$codeStart = $this->getMacroStartCode();

$codeEnd = $this->getMacroCodeEnd($expression);

$view = $this->extractFindableViewForMacro($expression);

$viewContent = $this->files->get($this->viewFinder->find($view));

$code = $codeStart.$viewContent."\n".$codeEnd;

$value = Str::replaceFirst($matches[0], $code, $value);
}

return $value;
}

/**
* Compile Blade comments into valid PHP.
*
Expand Down Expand Up @@ -1090,4 +1155,73 @@ public function setEchoFormat($format)
{
$this->echoFormat = $format;
}

/**
* Macro start code.
*
* @return string
*/
protected function getMacroStartCode()
{
$codeStart = <<<'HTML'
<?php
call_user_func(function ($macroName, array $firstMerging, array $secondMerging = null) {
if ($secondMerging !== null) {
$firstMerging = array_merge($secondMerging, $firstMerging);
}
$secondMerging = null;
extract($firstMerging);
?>
HTML;

return str_replace("\n", '', $codeStart);
}

/**
* Macro end code.
*
* @param string $expression
* @return string
*/
protected function getMacroCodeEnd($expression)
{
return sprintf("<?php }, %s, array_except(get_defined_vars(), array('__data', '__path'))) ;?>", $expression);
}

/**
* Extract findable view from macro.
*
* @param string $expression
* @return string
* @throws \ErrorException
*/
protected function extractFindableViewForMacro($expression)
{
$exploded = explode(',', $expression);

$view = trim($exploded[0]);

if (! Str::startsWith($view, "'") || ! Str::endsWith($view, "'") || substr_count($view, "'") !== 2) {
// What error to throw ?
throw new \ErrorException(
"The 'macro' directive can only be used with literal strings beginning and ending with \" ' \""
);
}

$view = str_replace('\'', '', $view);

$delimiter = ViewFinderInterface::HINT_PATH_DELIMITER;

if (strpos($view, $delimiter) === false) {
$view = str_replace('/', '.', $view);

return $view;
}

list($namespace, $view) = explode($delimiter, $view);

$view = $namespace.$delimiter.str_replace('/', '.', $view);

return $view;
}
}
2 changes: 1 addition & 1 deletion src/Illuminate/View/ViewServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public function registerBladeEngine($resolver)
$app->singleton('blade.compiler', function ($app) {
$cache = $app['config']['view.compiled'];

return new BladeCompiler($app['files'], $cache);
return new BladeCompiler($app['files'], $cache, $app['view.finder']);
});

$resolver->register('blade', function () use ($app) {
Expand Down
Loading