Sending variable from volt to custom function - twig

I have created a custom function I can access from the volt. The function seems to work fine, but I cannot manage to send the variable to the function. It sends the variable as text instead of its value.
The twig function:
$volt->getCompiler()->addFunction('getusergroup', function ($user) {
return \Models\User::getUserGroup($user);
});
The function in the Model:
public static function getUserGroup($user) {
return UserGroup::find(array('conditions' => 'user_id = ' . $user));
}
The lines in Twig to call the function:
{% for member in getusergroup(staff.id) %}
{{ member.Group.name }}
{% endfor %}
The error I get:
'Scanning error before 'staff->id' when parsing: SELECT
[Models\UserGroup].* FROM [Models\UserGroup] WHERE user_id =
$staff->id (78)' (length=131)
As you can see, in stead of $staff->id being an integer, it's the text.
How do I go about sending the actual ID to the function?
By the way, I am using twig in combination with Phalcon and followed the instructions in this article: http://phalcontip.com/discussion/60/extending-volt-functions

If you dump $user inside of your method public static function getUserGroup($user), you will receive this $staff->id, but you actually want something like 42.
To avoid this register the Volt function like this:
$volt->getCompiler()->addFunction('getusergroup', function ($resolvedArgs, $exprArgs) {
return 'Models\User::getUserGroup(' . $resolvedArgs . ')';
});
More info on Extending Volt Functions

Related

How to add common model to Twig and Slim4

I'm using Twig and Slim4 with DI container (the same as this tutorial: https://odan.github.io/2019/11/05/slim4-tutorial.html).
I would like to know how can I add a common model to all my twig views, for example user object, general options and something like this.
This is the container Twig initialization:
TwigMiddleware::class => function (ContainerInterface $container) {
return TwigMiddleware::createFromContainer($container->get(App::class), Twig::class);
},
// Twig templates
Twig::class => function (ContainerInterface $container) {
$config = $container->get(Configuration::class);
$twigSettings = $config->getArray('twig');
$twig = Twig::create($twigSettings['path'], $twigSettings['settings']);
return $twig;
},
The twig middleware is the Slim standard one: Slim\Views\TwigMiddleware
You can add global variables to Twig environment, so they are accessible in all template files:
(To be able to provide a sample code, I assumed you have defined a service like user-authentication-service which is capable of resolving current user)
// Twig templates
Twig::class => function (ContainerInterface $container) {
//...
$twig = Twig::create($twigSettings['path'], $twigSettings['settings']);
$twig->getEnvironment()->addGlobal(
'general_settings',
[
'site_name' => 'my personal website',
'contact_info' => 'me#example.com'
]);
$twig->getEnvironment()->addGlobal(
'current_user',
// assuming this returns current user
$container->get('user-authentication-service')->getCurrentUser()
);
return $twig;
},
Now you have access to general_settings and current_user in all of your template files.

Pomm Twig and relation

For a project I'm using pommbundle, it's perfect for generating entities with an existing database.
In my controller:
$catalogues = $this->get('pomm')['my_db1']
->getModel('\AppBundle\Entity\MyDb1\PublicSchema\CatalogueModel')
->findAll();
return this->render(
'SiteBundle:Default:homePage.html.twig',
array('catalogues'=>$catalogues));
But how can I access the variable inside my view (Twig)
{% for catalogue in catalogues %}
{{dump(catalogue)}} --> value inside
{% endfor %}
Result dump
Catalogue {#1132 ▼
#container: array:13 [▼
"ID" => 8
"Code" => "MATIÈRE PREMIÈRE"
"Actif" => true
"DateAjout" => DateTime {#1212 ▶}
"Index" => 0
"PriseCommande" => false
"Description" => ""
"Couleur" => "Green"
"CouleurText" => "#000000"
"Tarif" => null
"WebActif" => false
"WebTitre" => null
"WebDescription" => null ]
-status: 1 }
catalogue.ID (not working) catalogue.container.ID (not working)
with catalogue.get('ID') works but it's the best way?
Other question
If my entity has a relation, e.g. WebActif -> relation with another table,
How to access Webactif because the dump returns only an ID.Do I have to create my own method?
Is it possible to show a basic example?
The Model::findAll method returns an iterator on database results. When this iterator is traversed, it returns entities filled with converted values.
Note: you’d better not use upper case letters in your column names as it will lead to confusion and it will not work properly with Pomm flexible entities. (same applies for table names).
<dl>
{% if catalogues.isEmpty() %}
<dt>No results found.</dt>
{% else %}
<dt>There are {{ catalogues.count() }} results:</dt>
{% for catalogue in catalogues %}
<dd>{{ catalogue.code }} (added the {{ catalogue.date_ajout.format('d-m-Y') }}){% if catalogue.actif %} OK {% endif %}</dd>
{% endfor %}
{% endif %}
</dl>
Edit: Since your comment says your database contains capitalized column names here is an additional explanation on how flexible entities work.
(Official documentation about flexible entities is here)
When a flexible entity is hydrated by the iterator values, they are converted and then pushed with their name in the entity. This is why you can use the generic accessor $entity->get('MyColumn') because keys are preserved.
But flexible entities are strange beasts because they can change depending on the SELECT that decides the data sent to them. When such entity is created the getters and setters are virtually created using PHP’s __get and __set and __call functions.
This can seem weird but look at this example:
<?php
$entity = new MyEntity(['first_name' = 'John', 'last_name' => 'Doe']);
$entity['first_name']; // calls $entity->getFirstName() which defaults to $this->get('first_name');
It is then possible to override default accessors:
<?php
class MyEntity extends FlexibleEntity
{
/*
* Triggered by $entity['first_name']
* or $entity->first_name
*/
public function getFirstName(): string
{
return uc_words($this->get('first_name'));
}
public function getLastName(): string
{
return strtoupper($this->get('last_name'));
}
public function getName(): string
{
return sprintf("%s %s", $this->getFirstName(), $this->getLastName());
}
}
Then, in twig it is possible to simply do {{ entity.name }} to trigger the getName function.
As you can see, the column names are camel cased to create the virtual accessors, this operation can be reversed only if the original column names are in lower case.

Twig custom function with parameters

I read twig documentation, but I am little confused about custom functions and filters. I understand how to add custom functions. But I don't understand how to write a function that accepts some parameters, may be also some optional parameters.
For example, I have following pseudo code for function named sqare.
$twig = new Twig_Environment($loader);
$function = new Twig_SimpleFunction('square', function () {
if param2 present?
return param1*param2;
else
return param1;
});
$twig->addFunction($function);
Now what I want is that, param1 should have a default value 1 and param2 should be optional. The square function will return the product of the two parameters. I also want that if user do not pass the second parameter then param1 will be returned, that is the first parameter will be returned. How can I implement this? Also, should I call the function in the twig template as {{ square(5, 10) }}?
You need to define the parameters in your closure.
Twig will pass the parameters accordingly
$function = new Twig_SimpleFunction('square', function ($param1, $param2 = null) {
return isset($param2) ? $param1 * $param2 : $param1;
});
Then you call this function in Twig with :
Only one param : {{ square(5) }}
Two params : {{ square(5, 2) }}

phalcon framework -> edit model::find(); results in an foreach loop

As http://docs.phalconphp.com/en/latest/reference/models.html#understanding-records-to-objects says, you can edit the objects once its loaded in the memory.
$settingCategories = SettingCategory::find();
foreach($settingCategories as $settingCategory){
if($settingCategory->type == "2"){
$settingCategory->type = "asd";
$settingCategory->intersection = "asd";
}else{
$settingCategory->type = "blaa";
$settingCategory->intersection = "blaa";
}
$settingCategory->type = "test";
}
$this->view->setVar("settingCategories",$settingCategories);
type is still its default value when I loop through it with volt:
{% for settingCategory in settingCategories %}
<div class="tab-content">
<h4>{{ settingCategory.name }}</h4>
<h4>{{ settingCategory.type }}</h4> --> still (int) integer!?
<h4>{{ settingCategory.intersection }}</h4> --> undefined!?
</div>
{% endfor %}
When you are modifying a variable inside a foreach, you are modifying a "temporary variable". What it means is that since it is only a copy of the real variable, when you change it, the real value inside the array isn't changed. Now, on to what you could do to solve this:
Setters/Getters
I personally prefer this one. If what you want to do is data transformation (I.E. you change the value of a field from one thing to another, and you want to use the new value in your code everywhere), I would use setters and getters. Here is an example:
// This is inside your model
protected $type;
public function getType()
{
if ($this->type === 2) {
return "asd";
} else {
return $this->type;
}
}
public function setType($type)
{
if ($type === 2) {
$this->type = "asd";
} else {
$this->type = 1; // or $type, or anything really :)
}
}
Of course, in your code, you'll have to change $category->type to $category->getType() and $category->setType($type), based on whether you are reading the value or assigning something to it.
The Quick and Dirty Way
Well, if your use case is different, you can use your current code block with a simple modification. Change your foreach to foreach($settingCategories as &$settingCategory). The ampersand makes the variable be passed into the block as a reference (I.E. it is not a copy like your current case). That means changing it will change the real value.

Twig variables in twig variable

I have a twig variable html. To show it in a twig template I do {{html}}.
That variable looks like:
<div>{{region_top}}</div><div>{{region_center}}</div>
region_* is a variable too. When Twig parses my html variable, it doesn't parse the inner variables (regions).
What I should do?
I have twig variable html. To show it in twig template I do {{html}}. That variable look like {{region_top}}{{region_center}}. region_* is variables too. When twig parse my html variable he didn't parse inner variables (regions). What can I should do?
Twig takes your strings as a literal string, meaning you'll see the content of the variable, escaped. If you want it to be able to display {{region_top}} as well, I'd recommend something like this:
{{html|replace({'{{region_top}}': region_top, '{{region_center}}': region_center})}}
If the content of your html variable is also dynamic (meaning it can contain more than just those two variables), I'd write a twig plugin which can do what you want. Writing plugins is pretty easy to do.
EDIT: Here's the extension I just finished writing.
EDIT 2: The extension now uses the environment to render the string, so it evaluates the string, instead of just replacing variables. This means your variable can contain anything a template can, and it will be render and escaped by Twig itself. I'm awesome.
<?php
/**
* A twig extension that will add an "evaluate" filter, for dynamic evaluation.
*/
class EvaluateExtension extends \Twig_Extension {
/**
* Attaches the innervars filter to the Twig Environment.
*
* #return array
*/
public function getFilters( ) {
return array(
'evaluate' => new \Twig_Filter_Method( $this, 'evaluate', array(
'needs_environment' => true,
'needs_context' => true,
'is_safe' => array(
'evaluate' => true
)
))
);
}
/**
* This function will evaluate $string through the $environment, and return its results.
*
* #param array $context
* #param string $string
*/
public function evaluate( \Twig_Environment $environment, $context, $string ) {
$loader = $environment->getLoader( );
$parsed = $this->parseString( $environment, $context, $string );
$environment->setLoader( $loader );
return $parsed;
}
/**
* Sets the parser for the environment to Twig_Loader_String, and parsed the string $string.
*
* #param \Twig_Environment $environment
* #param array $context
* #param string $string
* #return string
*/
protected function parseString( \Twig_Environment $environment, $context, $string ) {
$environment->setLoader( new \Twig_Loader_String( ) );
return $environment->render( $string, $context );
}
/**
* Returns the name of this extension.
*
* #return string
*/
public function getName( ) {
return 'evaluate';
}
}
Example usage:
$twig_environment->addExtension( new EvaluateExtension( ) );
In the template:
{% set var = 'inner variable' %}
{{'this is a string with an {{var}}'|evaluate}}
See http://twig.sensiolabs.org/doc/functions/template_from_string.html
It seems that this is frequently missed as most folks think (and search for) "eval" when expecting a filter/function to evaluate in the current language they're drafting in. Template from string isn't the first search query that comes to mind.
One option is to render your templates as strings. You can do that like this:
$env = new \Twig_Environment(new \Twig_Loader_String());
echo $env->render(
"Hello {{ name }}",
array("name" => "World")
);
I'll leave it to you to decide how exactly to structure your code to make this work, but it might go something like this:
1) Fetch the inner template text that contains the variables that aren't being replaced.
2) Render that inner template text into an $html variable. Be sure to pass in any vars you need.
3) Render your original template that contains {{html}}. Be sure to pass in 'html' => $html in the vars array
You can also pass an array or an object to the view, and then use the twig attribute() method: http://twig.sensiolabs.org/doc/functions/attribute.html
{% if attribute(array, key) is defined %}
{{ attribute(array, key) }}
{% endif %}

Resources