How to render Laravel tags inside html stored in s3

324 Views Asked by At

Good day to all.

As the question title says, I do have HTML template code stored in s3 with Laravel tags on it, lets say:

<html>
<head>
    <title>{{ $landing->title }}</title>
</head>
<body>
@foreach($landing->products as $product)
    <p>{{ $product->title }}</p>
@endforeach
</body>
</html>

Then I would like to render this as post-processed html with the Laravel tags replaced as if it were a normal blade template.

In my controller I do have this:

print_r( view('render', compact('template', 'landing'))->render() );

(I don't want to show in it the browser, just get the html code)

And in the render.blade.php I have:

{!! html_entity_decode($template->html()) !!}

But this will show me the code with the Laravel tags without proper replacement.

Any lights on this? Any help will be appreciated.

1

There are 1 best solutions below

1
Kurt Friars On BEST ANSWER

I see two possible solutions to this problem.

  1. Compile the blade yourself (Not Optimal IMO)

You can make a helper function that will compile blade for you, given a string. (source)

helpers.php (Or wherever you want to locate the function)

function compile_blade($markup, $data = []) {
    $fs = new \Illuminate\Filesystem\Filesystem;
    $b = new \Illuminate\View\Compilers\BladeCompiler($fs, __DIR__);
    $src = $b->compileString($markup);

    $isPhp = false;
    if (substr( $src, 0, 5 ) === "<?php")
    {
        $isPhp = true;
        $src = substr($src, 5);
    }
    
    $tempFileName = tempnam("/tmp", "blade-compile");
    file_put_contents($tempFileName, $src);

    ob_start();

    extract($data);

    include $tempFileName;
    $out = ob_get_clean();
    if ($isPhp)
    {
        $out = '<?php'.$out;
    }
    
    return $out;
}

Then in your controller, you would pre-process the s3 blade for consumption in your render.blade.php file like:

    return view('render', [
        'template' => compile_blade($template, $landing),
        'landing' => $landing,
    ));

I don't think this is the optimal solution since you end up creating files anyways.

  1. Create a new namespace for blade/html coming from s3.

Firstly you need to create a folder in your project like ./storage/local/blade. Then you need to add a namespace for views in that folder like so:

AppServiceProvider.php

public function boot()
{
    ...

    view()->addNamespace('s3', storage_path('/local/views');
    ...
}

Now to handle retrieving the markup from s3 (in your controller, or elsewhere) you would do something like:

    // Lets say the file on s3 is markup.blade.php
    $contents = Storage::disk('s3')->get('path/to/markup.blade.php')
    Storage::disk('local')->put(storage_path('local/views/markup.blade.php'), $contents);

Now, if your render.blade.php is being used solely to render the markup on s3, you should just use the new namespaced view instead. You can use this in your controller like:

    return view('s3::markup', compact('landing'));

It becomes a bit more tricky if you want to use the s3 markup in one of your other blade files. But can be done by extending blade as in this post.

Blade::extend(function($view, $compiler)
{
    $pattern = $compiler->createMatcher('includeNamespaced');

    $viewPath = realpath($compiler->getPath());
    $parts = explode(DIRECTORY_SEPARATOR, $viewPath);
    $viewsDirectoryIndex = array_search('views', $parts);
    $namespace = $parts[$viewsDirectoryIndex + 1];

    $php = '$1<?php ';
    $php .= 'if($__env->exists(\''.$namespace.'.\'.$2)){';
    $php .= 'echo $__env->make(\''.$namespace.'.\'.$2)->render();';
    $php .= '}';
    $php .= 'else {';
    $php .= 'echo $__env->make($2)->render();';
    $php .= '}';
    $php .= '?>';

    return preg_replace($pattern, $php, $view);
});

Now you would be able to @include a namespaced view in your blade files like:

    @includeNamespaced('s3/markup')

The other reason I prefer solution 2, is that you can get some "caching" effect, if you look to see if the file already exists in local/views, before downloading from s3. Then you can create a scheduled job that deletes files in storage/local/views older than some time limit.