blade.php 9.06 KB
Newer Older
1
<?php namespace Laravel; use FilesystemIterator as fIterator;
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
		'layouts',
12
		'echos',
13 14 15
		'forelse',
		'empty',
		'endforelse',
16 17 18
		'structure_openings',
		'structure_closings',
		'else',
19
		'includes',
20 21
		'render_each',
		'render',
22 23
		'yields',
		'yield_sections',
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
		'section_start',
		'section_end',
	);

	/**
	 * 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))
			{
				return false;
			}

			$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.
50
			if ( ! file_exists($compiled) or Blade::expired($view->view, $view->path))
51
			{
52
				file_put_contents($compiled, Blade::compile($view));
53 54 55 56 57 58 59 60 61 62 63 64
			}

			$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();
		});
	}

	/**
65 66 67 68 69 70 71 72 73 74 75
	 * 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)
	{
		$compiled = static::compiled($path);

76
		return filemtime($path) > filemtime(static::compiled($path));
77 78 79
	}

	/**
80 81 82 83 84
	 * Compiles the specified file containing Blade pseudo-code into valid PHP.
	 *
	 * @param  string  $path
	 * @return string
	 */
85
	public static function compile($view)
86
	{
87
		return static::compile_string(file_get_contents($view->path), $view);
88 89 90 91 92 93
	}

	/**
	 * Compiles the given string containing Blade pseudo-code into valid PHP.
	 *
	 * @param  string  $value
94
	 * @param  View    $view
95 96
	 * @return string
	 */
97
	public static function compile_string($value, $view = null)
98 99 100 101 102
	{
		foreach (static::$compilers as $compiler)
		{
			$method = "compile_{$compiler}";

103
			$value = static::$method($value, $view);
104 105 106 107 108
		}

		return $value;
	}

109 110 111 112 113 114
	/**
	 * Rewrites Blade "@layout" expressions into valid PHP.
	 *
	 * @param  string  $value
	 * @return string
	 */
115
	protected static function compile_layouts($value)
116
	{
117 118
		// If the Blade template is not using "layouts", we'll just return it
		// it unchanged since there is nothing to do with layouts and we'll
119
		// just let the other Blade compilers handle the rest.
120
		if ( ! starts_with($value, '@layout'))
121 122 123 124
		{
			return $value;
		}

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

130
		$pattern = static::matcher('layout');
131

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

134 135 136 137
		// We will add a "render" statement to the end of the templates and
		// and then slice off the @layout shortcut from the start so the
		// sections register before the parent template renders.
		return implode(CRLF, array_slice($lines, 1));
138 139 140
	}

	/**
141
	 * Extract a variable value out of a Blade expression.
142 143 144 145
	 *
	 * @param  string  $value
	 * @return string
	 */
146
	protected static function extract($value, $expression)
147
	{
148
		preg_match('/@layout(\s*\(.*\))(\s*)/', $value, $matches);
149 150 151 152 153

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

	/**
154 155 156 157 158 159 160 161 162 163 164
	 * 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);
	}

	/**
165 166 167 168 169 170 171 172 173
	 * 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);

174
		foreach ($matches[0] as $forelse)
175
		{
176
			preg_match('/\$[^\s]*/', $forelse, $variable);
177

178 179 180 181
			// Once we have extracted the variable being looped against, we can add
			// an if statmeent to the start of the loop that checks if the count
			// of the variable being looped against is greater than zero.
			$if = "<?php if (count({$variable[0]}) > 0): ?>";
182

183
			$search = '/(\s*)@forelse(\s*\(.*\))/';
184

185
			$replace = '$1'.$if.'<?php foreach$2: ?>';
186

187
			$blade = preg_replace($search, $replace, $forelse);
188

189 190 191 192
			// Finally, once we have the check prepended to the loop we'll replace
			// all instances of this "forelse" syntax in the view content of the
			// view being compiled to Blade syntax with real syntax.
			$value = str_replace($forelse, $blade, $value);
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
		}

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

	/**
221 222 223 224 225 226 227 228 229 230 231 232 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
	 * 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);
	}

	/**
258 259 260 261 262 263 264 265 266
	 * Rewrites Blade @include statements into valid PHP.
	 *
	 * @param  string  $value
	 * @return string
	 */
	protected static function compile_includes($value)
	{
		$pattern = static::matcher('include');

267
		return preg_replace($pattern, '$1<?php echo view$2->with(get_defined_vars())->render(); ?>', $value);
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
	 * 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);
	}

	/**
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
	 * 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);
	}

	/**
324 325 326 327 328 329 330 331 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 361
	 * 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);
	}

	/**
	 * Get the regular expression for a generic Blade function.
	 *
	 * @param  string  $function
	 * @return string
	 */
	protected static function matcher($function)
	{
		return '/(\s*)@'.$function.'(\s*\(.*\))/';
	}

362
	/**
363
	 * Get the fully qualified path for a compiled view.
364
	 *
365 366
	 * @param  string  $view
	 * @return string
367
	 */
368
	public static function compiled($path)
369
	{
370
		return path('storage').'views/'.md5($path);
371 372
	}

373
}