blade.php 10.6 KB
Newer Older
1
<?php namespace Laravel; use FilesystemIterator as fIterator; use Closure;
2 3 4 5 6 7 8 9 10

class Blade {

	/**
	 * All of the compiler functions used by Blade.
	 *
	 * @var array
	 */
	protected static $compilers = array(
11
		'extensions',
12
		'layouts',
13
		'comments',
14
		'echos',
15 16 17
		'forelse',
		'empty',
		'endforelse',
18 19 20
		'structure_openings',
		'structure_closings',
		'else',
21 22
		'unless',
		'endunless',
23
		'includes',
24 25
		'render_each',
		'render',
26 27
		'yields',
		'yield_sections',
28 29 30 31 32
		'section_start',
		'section_end',
	);

	/**
33 34 35 36
	 * An array of user defined compilers.
	 *
	 * @var array
	 */
37
	protected static $extensions = array();
38 39

	/**
40 41 42 43 44 45 46 47 48 49 50 51 52
	 * Register the Blade view engine with Laravel.
	 *
	 * @return void
	 */
	public static function sharpen()
	{
		Event::listen(View::engine, function($view)
		{
			// The Blade view engine should only handle the rendering of views which
			// end with the Blade extension. If the given view does not, we will
			// return false so the View can be rendered as normal.
			if ( ! str_contains($view->path, BLADE_EXT))
			{
53
				return;
54 55 56 57 58 59 60
			}

			$compiled = path('storage').'views/'.md5($view->path);

			// If the view doesn't exist or has been modified since the last time it
			// was compiled, we will recompile the view into pure PHP from it's
			// Blade representation, writing it to cached storage.
61
			if ( ! file_exists($compiled) or Blade::expired($view->view, $view->path))
62
			{
63
				file_put_contents($compiled, Blade::compile($view));
64 65 66 67 68 69 70 71 72 73 74 75
			}

			$view->path = $compiled;

			// Once the view has been compiled, we can simply set the path to the
			// compiled view on the view instance and call the typical "get"
			// method on the view to evaluate the compiled PHP view.
			return $view->get();
		});
	}

	/**
Taylor Otwell committed
76
	 * Register a custom Blade compiler.
77 78
	 *
	 * <code>
Taylor Otwell committed
79 80 81
	 * 		Blade::extend(function($view)
	 *		{
	 * 			return str_replace('foo', 'bar', $view);
82 83 84
	 * 		});
	 * </code>
	 *
Taylor Otwell committed
85 86
	 * @param  Closure  $compiler
	 * @return void
87
	 */
Taylor Otwell committed
88
	public static function extend(Closure $compiler)
89
	{
90
		static::$extensions[] = $compiler;
91 92 93
	}

	/**
94 95 96 97 98 99 100 101 102
	 * Determine if a view is "expired" and needs to be re-compiled.
	 *
	 * @param  string  $view
	 * @param  string  $path
	 * @param  string  $compiled
	 * @return bool
	 */
	public static function expired($view, $path)
	{
103
		return filemtime($path) > filemtime(static::compiled($path));
104 105 106
	}

	/**
107 108 109 110 111
	 * Compiles the specified file containing Blade pseudo-code into valid PHP.
	 *
	 * @param  string  $path
	 * @return string
	 */
112
	public static function compile($view)
113
	{
114
		return static::compile_string(file_get_contents($view->path), $view);
115 116 117 118 119 120
	}

	/**
	 * Compiles the given string containing Blade pseudo-code into valid PHP.
	 *
	 * @param  string  $value
121
	 * @param  View    $view
122 123
	 * @return string
	 */
124
	public static function compile_string($value, $view = null)
125 126 127 128 129
	{
		foreach (static::$compilers as $compiler)
		{
			$method = "compile_{$compiler}";

130
			$value = static::$method($value, $view);
131 132 133 134 135
		}

		return $value;
	}

136 137 138 139 140 141
	/**
	 * Rewrites Blade "@layout" expressions into valid PHP.
	 *
	 * @param  string  $value
	 * @return string
	 */
142
	protected static function compile_layouts($value)
143
	{
144
		// If the Blade template is not using "layouts", we'll just return it
145
		// unchanged since there is nothing to do with layouts and we will
146
		// just let the other Blade compilers handle the rest.
147
		if ( ! starts_with($value, '@layout'))
148 149 150 151
		{
			return $value;
		}

152
		// First we'll split out the lines of the template so we can get the
153 154
		// layout from the top of the template. By convention it must be
		// located on the first line of the template contents.
155
		$lines = preg_split("/(\r?\n)/", $value);
156

157
		$pattern = static::matcher('layout');
158

159
		$lines[] = preg_replace($pattern, '$1@include$2', $lines[0]);
160

161
		// We will add a "render" statement to the end of the templates and
162
		// then slice off the "@layout" shortcut from the start so the
163 164
		// sections register before the parent template renders.
		return implode(CRLF, array_slice($lines, 1));
165 166 167
	}

	/**
168
	 * Extract a variable value out of a Blade expression.
169 170 171 172
	 *
	 * @param  string  $value
	 * @return string
	 */
173
	protected static function extract($value, $expression)
174
	{
175
		preg_match('/@layout(\s*\(.*\))(\s*)/', $value, $matches);
176 177 178 179 180

		return str_replace(array("('", "')"), '', $matches[1]);
	}

	/**
181 182 183 184 185 186 187 188 189 190 191 192 193
	 * Rewrites Blade comments into PHP comments.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_comments($value)
	{
		$value = preg_replace('/\{\{--(.+?)(--\}\})?\n/', "<?php // $1 ?>", $value);

		return preg_replace('/\{\{--((.|\s)*?)--\}\}/', "<?php /* $1 */ ?>\n", $value);
	}

	/**
194 195 196 197 198 199 200 201 202 203 204
	 * Rewrites Blade echo statements into PHP echo statements.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_echos($value)
	{
		return preg_replace('/\{\{(.+?)\}\}/', '<?php echo $1; ?>', $value);
	}

	/**
205 206 207 208 209 210 211 212 213
	 * Rewrites Blade "for else" statements into valid PHP.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_forelse($value)
	{
		preg_match_all('/(\s*)@forelse(\s*\(.*\))(\s*)/', $value, $matches);

214
		foreach ($matches[0] as $forelse)
215
		{
216
			preg_match('/\$[^\s]*/', $forelse, $variable);
217

218
			// Once we have extracted the variable being looped against, we can add
Taylor Otwell committed
219
			// an if statement to the start of the loop that checks if the count
220 221
			// of the variable being looped against is greater than zero.
			$if = "<?php if (count({$variable[0]}) > 0): ?>";
222

223
			$search = '/(\s*)@forelse(\s*\(.*\))/';
224

225
			$replace = '$1'.$if.'<?php foreach$2: ?>';
226

227
			$blade = preg_replace($search, $replace, $forelse);
228

229
			// Finally, once we have the check prepended to the loop we'll replace
230 231
			// all instances of this forelse syntax in the view content of the
			// view being compiled to Blade syntax with real PHP syntax.
232
			$value = str_replace($forelse, $blade, $value);
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
		}

		return $value;
	}

	/**
	 * Rewrites Blade "empty" statements into valid PHP.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_empty($value)
	{
		return str_replace('@empty', '<?php endforeach; ?><?php else: ?>', $value);
	}

	/**
	 * Rewrites Blade "forelse" endings into valid PHP.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_endforelse($value)
	{
		return str_replace('@endforelse', '<?php endif; ?>', $value);
	}

	/**
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
	 * Rewrites Blade structure openings into PHP structure openings.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_structure_openings($value)
	{
		$pattern = '/(\s*)@(if|elseif|foreach|for|while)(\s*\(.*\))/';

		return preg_replace($pattern, '$1<?php $2$3: ?>', $value);
	}

	/**
	 * Rewrites Blade structure closings into PHP structure closings.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_structure_closings($value)
	{
		$pattern = '/(\s*)@(endif|endforeach|endfor|endwhile)(\s*)/';

		return preg_replace($pattern, '$1<?php $2; ?>$3', $value);
	}

	/**
	 * Rewrites Blade else statements into PHP else statements.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_else($value)
	{
		return preg_replace('/(\s*)@(else)(\s*)/', '$1<?php $2: ?>$3', $value);
	}

	/**
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
	 * Rewrites Blade "unless" statements into valid PHP.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_unless($value)
	{
		$pattern = '/(\s*)@unless(\s*\(.*\))/';

		return preg_replace($pattern, '$1<?php if( ! ($2)): ?>', $value);
	}

	/**
	 * Rewrites Blade "unless" endings into valid PHP.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_endunless($value)
	{
		return str_replace('@endunless', '<?php endif; ?>', $value);
	}

	/**
322 323 324 325 326 327 328 329 330
	 * Rewrites Blade @include statements into valid PHP.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_includes($value)
	{
		$pattern = static::matcher('include');

331
		return preg_replace($pattern, '$1<?php echo view$2->with(get_defined_vars())->render(); ?>', $value);
332 333 334
	}

	/**
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
	 * Rewrites Blade @render statements into valid PHP.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_render($value)
	{
		$pattern = static::matcher('render');

		return preg_replace($pattern, '$1<?php echo render$2; ?>', $value);
	}

	/**
	 * Rewrites Blade @render_each statements into valid PHP.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_render_each($value)
	{
		$pattern = static::matcher('render_each');

		return preg_replace($pattern, '$1<?php echo render_each$2; ?>', $value);
	}

	/**
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
	 * Rewrites Blade @yield statements into Section statements.
	 *
	 * The Blade @yield statement is a shortcut to the Section::yield method.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_yields($value)
	{
		$pattern = static::matcher('yield');

		return preg_replace($pattern, '$1<?php echo \\Laravel\\Section::yield$2; ?>', $value);
	}

	/**
	 * Rewrites Blade yield section statements into valid PHP.
	 *
	 * @return string
	 */
	protected static function compile_yield_sections($value)
	{
		$replace = '<?php echo \\Laravel\\Section::yield_section(); ?>';

		return str_replace('@yield_section', $replace, $value);
	}

	/**
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
	 * Rewrites Blade @section statements into Section statements.
	 *
	 * The Blade @section statement is a shortcut to the Section::start method.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_section_start($value)
	{
		$pattern = static::matcher('section');

		return preg_replace($pattern, '$1<?php \\Laravel\\Section::start$2; ?>', $value);
	}

	/**
	 * Rewrites Blade @endsection statements into Section statements.
	 *
	 * The Blade @endsection statement is a shortcut to the Section::stop method.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_section_end($value)
	{
		return preg_replace('/@endsection/', '<?php \\Laravel\\Section::stop(); ?>', $value);
	}

	/**
416 417 418 419 420
	 * Execute user defined compilers.
	 *
	 * @param  string  $value
	 * @return string
	 */
421
	protected static function compile_extensions($value)
422
	{
423
		foreach (static::$extensions as $compiler)
424 425 426 427 428 429 430 431
		{
			$value = $compiler($value);
		}

		return $value;
	}	

	/**
432 433 434 435 436
	 * Get the regular expression for a generic Blade function.
	 *
	 * @param  string  $function
	 * @return string
	 */
437
	public static function matcher($function)
438 439 440 441
	{
		return '/(\s*)@'.$function.'(\s*\(.*\))/';
	}

442
	/**
443
	 * Get the fully qualified path for a compiled view.
444
	 *
445 446
	 * @param  string  $view
	 * @return string
447
	 */
448
	public static function compiled($path)
449
	{
450
		return path('storage').'views/'.md5($path);
451 452
	}

453
}