Mustache.php with JSON data throws Catchable fatal error - php

I've found out you can convert JSON file to PHP arrays and objects and use that output as a data for Mustache.php template engine like this example:
PHP Script:
require_once('Mustache/Autoloader.php');
Mustache_Autoloader::register();
$mustache = new Mustache_Engine();
$data_json = file_get_contents('data.json');
$data = json_decode( $data_json, true );
$template = $mustache -> loadTemplate('templates/template.html');
echo $mustache -> render( $template, $data );
JSON data:
{
"persons": [{
"person": {
"name": "Homer",
"number": 0
},
"person": {
"name": "Marge",
"number": 1
}
}]
}
Mustache template:
{{{# persons }}}
{{{# person }}}
<ul>
<li>Name: {{name}}</li>
<li>Number: {{number}}</li>
</ul>
{{{# person }}}
{{{/ persons }}}
But PHP throws this error:
Catchable fatal error:
Object of class __Mustache_12af6f5d841b135fc7bfd7d5fbb25c9e could not be converted to string in C:\path-to-mustache-folder\Engine.php on line 607
And this is where PHP points that error came from (Inside Engine.php file in above error):
/**
* Helper method to generate a Mustache template class.
*
* #param string $source
*
* #return string Mustache Template class name
*/
public function getTemplateClassName($source)
{
return $this->templateClassPrefix . md5(sprintf(
'version:%s,escape:%s,entity_flags:%i,charset:%s,strict_callables:%s,pragmas:%s,source:%s',
self::VERSION,
isset($this->escape) ? 'custom' : 'default',
$this->entityFlags,
$this->charset,
$this->strictCallables ? 'true' : 'false',
implode(' ', $this->getPragmas()),
$source
));
}
I just know somehing is wrong in data conversation but I'm not familiar to PHP debugging and this is for experimental use, I appreciate if you could tell me what is wrong.

The $template argument of Mustache_Engine::render must be a string; however Mustache_Engine::loadTemplate returns an instance of the Mustache_Template class (which Mustache subsequently tries to treat as a string, which fails).
You should be able to invoke the render(...) method on the template object instead (untested, though):
$template = $mustache->loadTemplate(...);
$renderedContent = $template->render($data);
I'm not that familiar with Mustache, but according to the documentation, by default loadTemplate needs to be invoked with the template string, and not a template file name. Consider also configuring a FileSystemLoader for loading your templates:
$mustache = new Mustache_Engine(array(
'loader' => new Mustache_Loader_FilesystemLoader($pathToTemplateDir),
));

I think mistake is here:
{{{# person }}}
{{{/ persons }}}
Try:
{{{#persons}}}
{{{#person}}}
<ul>
<li>Name: {{name}}</li>
<li>Number: {{number}}</li>
</ul>
{{{/person}}}
{{{/persons}}}

Related

Using record value as Twig function parameter

I'm working on writing a Bolt extension to parse an XML file and return the deserialised object tree for the XML. The parsing and deserialising is working like a charm but I cannot get the filename from the page record working as a parameter for the Twig function.
Extension code (simplified) is:
class CompetitionExtension extends SimpleExtension
{
protected function registerTwigFunctions()
{
return [
'competition' => 'competitionFunction',
];
}
/**
* Load and parse the competition XML.
*
* #param string $filename
* #return Competition
*/
public function competitionFunction(string $filename) : Competition
{
$competition = null;
$loader = new FileLoader($filename);
if ($loader->openFile()) {
$competition = $loader->parse();
}
return $competition;
}
}
The contenttype (extended the homepage) addition is:
competitionxml:
type: file
upload: competitions
group: content
Upload of the file is also no problem but the following Twig template code gives an error:
{% set xmlfile = homepage.competitionxml %}
{% set comp = competition(xmlfile) %}
Using dump I see that xmlfile has the correct value. But I get the following error:
An exception has been thrown during the rendering of a template ("Notice: Undefined variable: filename") in "index.twig" at line 27.
So how can I use the filename of the attachment as parameter for the Twig function?

Indirect Modification of Overloaded Property Laravel MongoDB

I am using MongoDB with Laravel. I have a collection called categories which has one document
[
{
"_id": "567dc4b4279871d0068b4568",
"name": "Fashion",
"images": "http://example.com/1.jpg",
"specifics": [
"made"
],
"brands": [
{
"name": "Giordano",
"logo": "http://example.com/"
},
{
"name": "Armani",
"logo": "http://example.com/"
}
],
"updated_at": "2015-12-25 22:40:44",
"created_at": "2015-12-25 22:35:32"
}
]
I am trying to make a function that add specifics to the specifics array in the above document.
Here is how my request body is
HTTP: POST
{
"specifics": [
"material"
]
}
And i am handling this request with the following function
/**
* Add specs to category
* #param string $category_id
* #return Illuminate\Http\JsonResponse
*/
public function addSpecifics($category_id)
{
$category = $this->category->findOrFail($category_id);
$category->specifics[] = $this->request->get('specifics');
$status_code = config('http.UPDATED');
return response()->json($category->save(), $status_code);
}
But when i hit this call, I get error of
ErrorException in CategoryController.php line 101: Indirect
modification of overloaded property App\Category::$specifics has no
effect
Please help me in fixing this.
I am using https://github.com/jenssegers/laravel-mongodb this package for MongoDB.
Due to how accessing model attributes is implemented in Eloquent, when you access $category->specifics, a magic __get() method is called that returns a copy of that attribute's value. Therefore, when you add an element to that copy, you're just changing the copy, not the original attribute's value. That's why you're getting an error saying that whatever you're doing, it won't have any effect.
If you want to add a new element to $category->specifics array, you need to make sure that the magic __set() is used by accessing the attribute in a setter manner, e.g.:
$category->specifics = array_merge($category->specifics, $this->request->get('specifics'));
You can use this method in case that you want to add a single item to the array:
PHP:
$new_id = "1234567";
$object->array_ids = array_merge($object->array_ids, [$new_id]);
This work's for me!
Store your variable in a temp variable:
// Fatal error
$foo = array_shift($this->someProperty);
// Works fine
$tmpArray = $this->someProperty;
$foo = array_shift($tmpArray);
I had the same problem with array inside of my class which extends Model.
This code $this->my_array[$pname]=$v; did not work until I declare protected $my_array = [];
Another simple alternative:
function array_push_overloaded($source, $element) {
$source[] = $element;
return $source;
}
Example:
$category->specifics = array_push_overloaded($category->specifics, $this->request->get('specifics'));
Don't try to directly modify an Eloquent collection, instead cast the collection to an Array and use that array for your modifications.
$model = Model::findOrFail($id);
$array = $model->toArray();
$array['key'] = $value;

Twig tag or function inside PHP String

Is there any way to use something like this?
$foo = "{{ object|filter }}";
Because I'm trying to write a dynamic image converter that needs to output something like the example, but when in my twig I use {{ foo }}, it just outputs a raw string {{ object|filter }} instead of executhing the filter on the object as intended.
I've tried to use {{ foo | raw }} but same result.
What I'm trying to do exactly
CONTROLLER
$image = $em->getRepository('AcmeDemo:Media')->find($id);
$image_src = sprintf("{{ %s | imagine_filter('%s') }}", $image->getWebPath(), 'front_small');
return $this->render('image.html.twig', array(
'image_src' => $image_src
));
TWIG
<img src="{{ image_src }}"/>
So, I have a twig function inside a PHP variable $image_src, that Twig function could be, once formatted with sprintf {{ '/uploads/foo.jpg' | imagine_filter('front_small') }}.
That is a string for now, because it's inside a php variable $image_src, that variable is sent to my Twig template with the name image_src so, for now it is a string as I've said, if I do
My variable contains "{{ image_src }}" It will output a string that says:
My variable contains "{{ '/uploads/foo.jpg' | imagine_filter('front_small') }}"
Because, as I've said, image_src is just a string, but I want to acutally execute inside my Twig, the string that contains image_src, because yes, it is a string (to the eyes of the compiler) but we all know it is or it is pretended to be a Twig function (because of the syntax).
So, why | raw will not work?, because it is inteded to be used with strings containing HTML code, if it were HTML syntax it would work, but it's a Twig syntax, so It doesn't work.
Resuming, there should be a | compile twig function that executes Twig code inside a variable like | raw does with HTML, but, as this function doesn't exists, I'm wondering if there's a way to achieve it...
As #joshua said, it's like a Javascript eval.
I hope I've explained good what is the problem and what I need.
EDIT
I've used my own twig extension Compile in order to achieve what I needed.
class CompileExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
'compile' => new \Twig_Filter_Method($this, 'compile', array(
'needs_environment' => true,
'needs_context' => true,
'is_safe' => array('compile' => true)
)),
);
}
public function compile(\Twig_Environment $environment, $context, $string)
{
$loader = $environment->getLoader();
$compiled = $this->compileString($environment, $context, $string);
$environment->setLoader($loader);
return $compiled;
}
public function compileString(\Twig_Environment $environment, $context, $string)
{
$environment->setLoader(new \Twig_Loader_String());
return $environment->render($string, $context);
}
public function getName()
{
return 'compile';
}
}
UPDATE
Accepting #Benjamin Paap answer because it does exactly what I wanted in this case with better code, but my custom Twig class works for every situation.
What you want to do is not possible in twig without a TwigExtension which renders your string separately.
But looking at your code you're trying to use the LiipImagineBundle the wrong way. It seems tempting to use it this way, but the correct way to generate a url for your thumbnails would be this:
class MyController extends Controller
{
public function indexAction()
{
// RedirectResponse object
$imagemanagerResponse = $this->container
->get('liip_imagine.controller')
->filterAction(
$this->request, // http request
'uploads/foo.jpg', // original image you want to apply a filter to
'my_thumb' // filter defined in config.yml
);
// string to put directly in the "src" of the tag <img>
$cacheManager = $this->container->get('liip_imagine.cache.manager');
$srcPath = $cacheManager->getBrowserPath('uploads/foo.jpg', 'my_thumb');
// ..
}
}
https://github.com/liip/LiipImagineBundle#using-the-controller-as-a-service

Zend: Generating partial in command line

I have set of class that send mails to my coworkers, while specific event happens. I had no problem with this so far, until today. I have to run some code from CLI (target - cron) to send daily reports. As you might suspect - no controller nor view is involved.
My code:
/**
* Returns mail content
*
* #param Crm_Mail_Abstract_AlertMail $object
* #return string
* #throws Exception
*/
private function generateContent(Crm_Mail_Abstract_AlertMail $object)
{
$subContent = $object->getInnerContent();
if (!$subContent) {
throw new Exception('Cannot load subcontent');
}
$view = Zend_Layout::getMvcInstance()->getView();
$content = $view->partial($this->mainLayout, array('content' => $subContent));
return $content;
}
And the error I get:
PHP Fatal error: Call to a member function getView() on a non-object
in /home/..../library/Crm/Mail/AlertMail.php on line 195
So... how to render partials while in terminal?
According to This : http://framework.zend.com/manual/1.12/en/zend.layout.quickstart.html
// Returns null if startMvc() has not first been called
$layout = Zend_Layout::getMvcInstance();
So, if you didn't added this :
// In your bootstrap:
Zend_Layout::startMvc();
You will not access this -
Zend_Layout::getMvcInstance()->getView()
So try do this something like that:
Zend_Layout::startMvc();
Zend_Layout::getMvcInstance()->getView();

Is there any way to compile a blade template from a string?

How can I compile a blade template from a string rather than a view file, like the code below:
<?php
$string = '<h2>{{ $name }}</h2>';
echo Blade::compile($string, array('name' => 'John Doe'));
?>
http://paste.laravel.com/ujL
I found the solution by extending BladeCompiler.
<?php namespace Laravel\Enhanced;
use Illuminate\View\Compilers\BladeCompiler as LaravelBladeCompiler;
class BladeCompiler extends LaravelBladeCompiler {
/**
* Compile blade template with passing arguments.
*
* #param string $value HTML-code including blade
* #param array $args Array of values used in blade
* #return string
*/
public function compileWiths($value, array $args = array())
{
$generated = parent::compileString($value);
ob_start() and extract($args, EXTR_SKIP);
// We'll include the view contents for parsing within a catcher
// so we can avoid any WSOD errors. If an exception occurs we
// will throw it out to the exception handler.
try
{
eval('?>'.$generated);
}
// If we caught an exception, we'll silently flush the output
// buffer so that no partially rendered views get thrown out
// to the client and confuse the user with junk.
catch (\Exception $e)
{
ob_get_clean(); throw $e;
}
$content = ob_get_clean();
return $content;
}
}
Small modification to the above script.
You can use this function inside any class without extending the BladeCompiler class.
public function bladeCompile($value, array $args = array())
{
$generated = \Blade::compileString($value);
ob_start() and extract($args, EXTR_SKIP);
// We'll include the view contents for parsing within a catcher
// so we can avoid any WSOD errors. If an exception occurs we
// will throw it out to the exception handler.
try
{
eval('?>'.$generated);
}
// If we caught an exception, we'll silently flush the output
// buffer so that no partially rendered views get thrown out
// to the client and confuse the user with junk.
catch (\Exception $e)
{
ob_get_clean(); throw $e;
}
$content = ob_get_clean();
return $content;
}
For anyone still interested in this, they've added it to Laravel 9
use Illuminate\Support\Facades\Blade;
return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);
https://laravel.com/docs/9.x/blade#rendering-inline-blade-templates
I just stumbled upon the same requirement! For me, i had to fetch a blade template stored in DB & render it to send email notifications.
I did this in laravel 5.8 by kind-of Extending \Illuminate\View\View. So, basically i created the below class & named him StringBlade (I couldn't find a better name atm :/)
<?php
namespace App\Central\Libraries\Blade;
use Illuminate\Filesystem\Filesystem;
class StringBlade implements StringBladeContract
{
/**
* #var Filesystem
*/
protected $file;
/**
* #var \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
protected $viewer;
/**
* StringBlade constructor.
*
* #param Filesystem $file
*/
public function __construct(Filesystem $file)
{
$this->file = $file;
$this->viewer = view();
}
/**
* Get Blade File path.
*
* #param $bladeString
* #return bool|string
*/
protected function getBlade($bladeString)
{
$bladePath = $this->generateBladePath();
$content = \Blade::compileString($bladeString);
return $this->file->put($bladePath, $content)
? $bladePath
: false;
}
/**
* Get the rendered HTML.
*
* #param $bladeString
* #param array $data
* #return bool|string
*/
public function render($bladeString, $data = [])
{
// Put the php version of blade String to *.php temp file & returns the temp file path
$bladePath = $this->getBlade($bladeString);
if (!$bladePath) {
return false;
}
// Render the php temp file & return the HTML content
$content = $this->viewer->file($bladePath, $data)->render();
// Delete the php temp file.
$this->file->delete($bladePath);
return $content;
}
/**
* Generate a blade file path.
*
* #return string
*/
protected function generateBladePath()
{
$cachePath = rtrim(config('cache.stores.file.path'), '/');
$tempFileName = sha1('string-blade' . microtime());
$directory = "{$cachePath}/string-blades";
if (!is_dir($directory)) {
mkdir($directory, 0777);
}
return "{$directory}/{$tempFileName}.php";
}
}
As you can already see from the above, below are the steps followed:
First converted the blade string to the php equivalent using \Blade::compileString($bladeString).
Now we have to store it to a physical file. For this storage, the frameworks cache directory is used - storage/framework/cache/data/string-blades/
Now we can ask \Illuminate\View\Factory native method 'file()' to compile & render this file.
Delete the temp file immediately (In my case i didn't need to keep the php equivalent file, Probably same for you too)
And Finally i created a facade in a composer auto-loaded file for easy usage like below:
<?php
if (! function_exists('string_blade')) {
/**
* Get StringBlade Instance or returns the HTML after rendering the blade string with the given data.
*
* #param string $html
* #param array $data
* #return StringBladeContract|bool|string
*/
function string_blade(string $html, $data = [])
{
return !empty($html)
? app(StringBladeContract::class)->render($html, $data)
: app(StringBladeContract::class);
}
}
Now i can call it from anywhere like below:
<?php
$html = string_blade('<span>My Name is {{ $name }}</span>', ['name' => 'Nikhil']);
// Outputs HTML
// <span>My Name is Nikhil</span>
Hope this helps someone or at-least maybe inspires someone to re-write in a better way.
Cheers!
I'm not using blade this way but I thought that the compile method accepts only a view as argument.
Maybe you're looking for:
Blade::compileString()
It's a old question. But I found a package which makes the job easier.
Laravel Blade String Compiler renders the blade templates from the string value. Check the documentation on how to install the package.
Here is an example:
$template = '<h1>{{ $name }}</h1>'; // string blade template
return view (['template' => $template], ['name' => 'John Doe']);
Note: The package is now updated to support till Laravel 6.
I know its pretty old thread, but today also requirement is same.
Following is the way I solved this on my Laravel 5.7 (but this will work with any laravel version greater than version 5), I used the knowledge gained from this thread and few other threads to get this working (will leave links to all threads at the end, if this help up-vote those too)
I added this to my helper.php (I used this technique to add helper to my project, but you can use this function directly as well)
if (! function_exists('inline_view')) {
/**
* Get the evaluated view contents for the given blade string.
*
* #param string $view
* #param array $data
* #param array $mergeData
* #return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
function inline_view($view = null, $data = [], $mergeData = [])
{
/* Create a file with name as hash of the passed string */
$filename = hash('sha1', $view);
/* Putting it in storage/framework/views so that these files get cleared on `php artisan view:clear*/
$file_location = storage_path('framework/views/');
$filepath = storage_path('framework/views/'.$filename.'.blade.php');
/* Create file only if it doesn't exist */
if (!file_exists($filepath)) {
file_put_contents($filepath, $view);
}
/* Add storage/framework/views as a location from where view files can be picked, used in make function below */
view()->addLocation($file_location);
/* call the usual view helper to render the blade file created above */
return view($filename, $data, $mergeData);
}
}
Usage is exactly same as laravel's view() helper, only that now first parameter is the blade string
$view_string = '#if(strlen($name_html)>6)
<strong>{{ $name_html }}</strong>
#else
{{$name_html}}
#endif';
return inline_view($view_string)->with('name_html', $user->name);
return inline_view($view_string, ['name_html' => $user->name]);
References:
https://stackoverflow.com/a/31435824/4249775
https://stackoverflow.com/a/33594452/4249775
Laravel 9 :
use Illuminate\Support\Facades\Blade;
return Blade::render('Your Blade Content {{ $parameter1}}', ['parameter1' => 'Name']);

Categories