key value being replaced by 'key' when using merge() in twig - php

I am trying to add pairs of key value to an array with their current values for all those attributes not starting by '_'. For some reason, the merge replaces the value of "key" (i.e slug) with the string 'key'.
For example when slug is the only attribute with key not starting with '_',
key = slug
value = something
it behaves as follows:
{% for key,value in app.request.attributes.all %}
{% if '_' != key | slice(0, 1) %}
{{ dump(key) }} // string(4) "slug"
{% set params = params | merge({ key : value}) %}
{{ dump(key) }} // string(4) "slug"
{% endif %}
{% endfor %}
{{ dump(params) }} // array(1) { ["key"]=> string(9) "something" }
I have added what the dumps return next to them.
The final dump returns
array(1) { ["key"]=> string(9) "something" }
while i'm expecting
array(1) { ["slug"]=> string(9) "something" }
I'd say it's a similar problem to Twig forgets array-keys but the conclusion on that question is that is a mongodb problem and I am not using it. I am working with attributes from the request.
For some reason, the merge({ key : value}) is behaving as merge({ 'key' : value}).

You need to wrap your variable with parenthesis to be able to use it as a key.
{% set params = params | merge({ (key) : value}) %}

With numeric keys you can lose your key in the process, with the m̀erge`filter.
I couldn't find any documentation about the '+' operator applied with arrays, but it works well in this case:
{% set array = {(1): 2} + array %}
Source: https://github.com/twigphp/Twig/issues/2741#issuecomment-417445042

Related

Twig - getting text filed key and value

I hav text filed where I stored some json data.
I am getting in my twig template on dump like:
"{"birthDate":"2021-09-01", "amount":8}"
I want to access keys so I can extract it's values.
Tried with json_encode or:
{% for key, value in item.value %}
but nothing seems to work.
Is the problem with the output data?
It seems that your item is neither object nor array it seems to be a string.
Try to convert your data to object or array json as below:
$objJson = json_decode($yourDBArray['menu']);
$arrJson = json_decode($yourDBArray['menu'],true);
Then you can use to show as below
{% for key,value in menu %}
Key : {{ key }}
Value : {{ value }}
{% endfor %}

How can I use Advanced Custom Fields with timber to retrieve Array values?

I'm trying to retrieve the user's metadata by first calling a custom field (using advanced custom fields plugin), using array values, the problem is that I could do that using PHP, but I have to use Timber because of my theme and there's not much info out there teaching how to use Timber and ACF (using advanced custom fields), even the info available is confusing and poor. I'm using Wordpress with Gantry5 framework and Helium theme.
First I set the custom field in ACF as "relational > user", then I set the data format as User Array, save, then I open a post and setup the fields inside the post, by choosing the user of each field.
So let's say the field name is "post_autor" and I need to display it below the post, the only problem is that I need to retrieve its arrays, so this is what I've tried to find the array values:
{% set author = post.get_field("post_autor") %} then {{ dump() }}
That line of code goes here:
“themes/g5_helium/custom/views/partials/content-single.html.twig”, after this part:
{# Begin Page Content #}
{{ post.paged_content|raw }}
{{ function('wp_link_pages', {'before': '<div class="page-links" itemprop="pagination"><ul class="pagination-list">', 'after': '</ul></div>', 'link_before': '<span class="page-number page-numbers">', 'link_after': '</span>', 'echo': 0}) }}
{# End Page Content #}
And this is the result I'm getting:
then array(1) { [0]=> array(11) { ["ID"]=> int(1) ["user_firstname"]=> string(7) "John" ["user_lastname"]=> string(5) "Doe" ["nickname"]=> string(4) "john" ["user_nicename"]=> string(13) "johndoe" ["display_name"]=> string(13) "John Doe" ["user_email"]=> string(23) "contact#site.com" ["user_url"]=> string(23) "https://siteDOTcom" ["user_registered"]=> string(19) "2019-03-12 03:53:10" ["user_description"]=> string(0) "" ["user_avatar"]=> string(472) "John Doe" } }
Of course I changed some data before posting here, because it's personal data.
I believe that this tutorial https://www.advancedcustomfields.com/resources/querying-relationship-fields/ has something to do with what I'm trying to achieve, but I'm not sure.
Basically it's this:
Field name --
=> Array value 1
=> Array value 2
=> Array value 3
First I need to get the field, then retrieve its "sub" values and display them. I tried to do my best to explain this, if anyone else needs more info, just ask.
Thanks in advance.
To access elements from a 2d array you can do {{ author.user_firstname }} or if there were further level you can do {{ author.level1.level2 }}
FYI, you can still use php and then add the data to your twig files by extending the context.
For example in your single.php
// usual php stuff
$logic = some_logic_function();
// get context
$context = Timber::context();
// add to context
$context['logic'] = $logic;
// render view
Timber::render('single.twig', $context);
Then in your single.twig file you can access the data as such:
{{ logic }}
as indicated in your comment, acf fields can be accessed in much the same way as you'd access other meta properties.
if it's a repeater, it'll have multiple values, so you can loop over them (only use .meta for the 'parent' property)
code sample for template.twig:
{% if post.meta('partner_organisations')|length %}
{% for item in post.meta('partner_organisations') %}{% if item.status == "publish" %}
{{ item.name }}<br />{% endif %}
{% endfor %}
{% endif %}
here, first we make sure there's actually some values. as this is for Posts, they have a status which might be draft, so we check for a published status before displaying the linked name. if you wanted an unordered list, you'd add the <ul> tags inside the if
you can also build an array with the relevant references in your template.php before passing it over to twig.
other ways of interacting with arrays/lists in timber/twig:
{{ dump(post.meta('list_field')|first) }}
{{ post.meta('list_field')|join(', ') }}
{{ post.meta('list_field')|join(', ', ' and ') }}
documentation on nested repeaters and more is available here https://timber.github.io/docs/guides/acf-cookbook/#nested-repeater-fields
if you're looking for details specifically using a relationship field - the doc above also has info on that, as you;d want to make sure it's a Timber\Post so you can interact with its properties as usual:
{% for item in Post(post.relationship_field) %}
{{ item.title }}
{# Do something with item #}
{% endfor %}

Twig merge for json with string of numbers as key

I'm trying to merge hashes with Twig to output JSON.
My problem is that some of my keys are using strings of numbers and twig converts it to integers.
My code :
{% set rows = {} %}
{% for key, val in row %}
{% set rows = rows|merge({ (key) : val }) %}
{% endfor %}
{{ { 'report': { 'metric': metric, 'rows': rows, 'tot': tot, 'min': min, 'max': max } }|json_encode|raw }}
Which outputs
{"report":{"metric":"sessions","rows":["5","4","4","3","7","4","4"],"tot":"31","min":"0","max":"7"}}
I also tried replacing my keys with number_format, but since I'm stripping all non-numeric characters, the output is the same.
{% set rows = {} %}
{% for key, val in row %}
{% set rows = rows|merge({ (key)|number_format(0,'','') : val }) %}
{% endfor %}
{{ { 'report': { 'metric': metric, 'rows': rows, 'tot': tot, 'min': min, 'max': max } }|json_encode|raw }}
The result expected goes like this:
{"report":{"metric":"sessions","rows":{"20180423":"5","20180424":"4","20180425":"4","20180426":"3","20180427":"7","20180428":"4","20180429":"4"},"tot":"31","min":"0","max":"7"}}
Is there any way I can prevent Twig from changing my keys to integers?
Found this post, but it doesn't work for me since my keys are strings of numbers.
key value being replaced by 'key' when using merge() in twig
Twig's merge filter relies on PHP's array_merge function and the doc says :
Values in the input array with numeric keys will be renumbered with
incrementing keys starting from zero in the result array.
And string containing only numbers are considered numeric.
Solution:
The simplest solution would be to change the keys format from "20180423" to "2018-04-23" which would make it non-numeric.
If you really need to keep your numeric keys, you could create a custom filter to merge arrays the way you want:
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class AppExtension extends AbstractExtension
{
public function getFilters()
{
return array(
new TwigFilter('mymerge', array($this, 'merge')),
);
}
public function merge($baseArray, $arrayToMerge)
{
foreach ($arrayToMerge as $key => $value) {
$baseArray[$key] = $value;
}
return $baseArray;
}
}
Then
{% set test = {"1234": "2", "2345": "3"} %}
{% set rows = {"test": "1"} %}
{% set rows = rows|mymerge(test) %}
{{ { 'report': { 'rows': rows } }|json_encode|raw }}
Would output
{"report":{"rows":{"test":"1","1234":"2","2345":"3"}}}

Object variable don't display in a twig template

I find this strange behaviour when using Twig (v1.23.3)
In a templace I have an array of objects : I use the dump function to display it, like this :
<pre align="left" style="text-align: left; font-size: 80%">
{{ dump(current_balances) }}
</pre>
{% for key, val in current_balances %}
{{ val.name }} : {{ val.balance | number_format(2, '.', ' ') }} €<br>
{% else %}
Aucun solde trouvé.
{% endfor %}
The output of dump is as expected :
array(2) {
[0]=>
object(Record)#20 (3) {
["name"]=>
string(32) "Bank 1 - Account"
["balance"]=>
string(7) "2000.00"
}
[1]=>
object(Record)#21 (3) {
["name"]=>
string(32) "Bank 2 - Account"
["balance"]=>
string(8) "1000.00"
}
}
But the output of the for loop is not correct. {{ val.name }} is displayed, but {{ val.balance }} is empty.
I dit this kind of loops hundreds of time - this code is merely cut-and-paste from a template that works.
Event more curious is that, if I invert the order of the vars in the object (as is it matters), putting balance before name, then balance is displayed, and not name !
Don't really know where to look with this one. Of course, my template are rendered with controllers calling models, so this is only a little part of the code.
I think I found a bug, similar to Variables not replaced in Twig template but it's hard to tell.
That's not a bug ! I finally enable the cache in Twig and looked at the resulting code. Guess what ? Some non-printable char has find its way near the val.balance. A "cat -A" on the template file shows this :
{{ val.name }} : {{M-BM- val.balance }} M-bM-^BM-,<br>$
And result in this in the compiled template :
$this->getAttribute((isset($context[" val"]) ? $context[" val"] : null)
Noticed the space before val ?
Lost one hour with this one. Source problem is an obsolete frenck keymap in Debian....

Setting element of array from Twig

How can I set member of an already existing array from Twig?
I tried doing it next way:
{% set arr['element'] = 'value' %}
but I got the following error:
Unexpected token "punctuation" of value "[" ("end of statement block"
expected) in ...
There is no nice way to do this in Twig. It is, however, possible by using the merge filter:
{% set arr = arr|merge({'element': 'value'}) %}
If element is a variable, surround it with brackets:
{% set arr = arr|merge({(element): 'value'}) %}
I ran into this problem but was trying to create integer indexes instead of associative index like 'element'.
You need to protect your index key with () using the merge filter as well:
{% set arr = arr|merge({ (loop.index0): 'value'}) %}
You can now add custom index key like ('element'~loop.index0)
If initialization only need:
{% set items = { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'unknown' } %}
I have tried #LivaX 's answer but it does not work , merging an array where keys are numeric wont work ( https://github.com/twigphp/Twig/issues/789 ).
That will work only when keys are strings
What I did is recreate another array ( temp) from the initial array (t) and make the keys a string , for example :
{% for key , value in t%}
{% set temp= temp|merge({(key~'_'):value}) %}
{% endfor %}
t keys : 0 , 1 , 2 ..
temp keys : 0_, 1_ , 2_ ....
You can also use the following syntax:
{% set myArray = myArray + myArray2 %}
Just use this like {% set arr={'key':'value'} %} (with no blank space after the :), it works well.
But when I use it inside a for loop, to make it an array, it does not work outside of the for scope.
{% for group in user.groups %}
{% set foo={'loop.index0':'group.id'} %}
{% set title={'loop.index0':'group.title'} %}
{{ title }} //it work
{% else %}
{% set foo={'0':'-1'} %}
{% set title={'0':'未分组'} %}
{% endfor %}
{{ title }} //it does not work, saying title is not defined
{% set links = {} %}
{# Use our array to wrap up our links. #}
{% for item in items %}
{% set links = links|merge({ (loop.index0) : {'url': item.content['#url'].getUri(), 'text': item.content['#title']} }) %}
{% endfor %}
{%
set linkList = {
'title': label,
'links': links
}
%}
{% include '<to twig file>/link-list.twig'%}
Thanks for this thread -- I was also able to create an array with (loop.index0) and send to twig.
I've found this issue very annoying, and my solution is perhaps orthodox and not inline with the Twig philosophy, but I developed the following:
$function = new Twig_Function('set_element', function ($data, $key, $value) {
// Assign value to $data[$key]
if (!is_array($data)) {
return $data;
}
$data[$key] = $value;
return $data;
});
$twig->addFunction($function);
that can be used as follows:
{% set arr = set_element(arr, 'element', 'value') %}
Adding my answer in case anyone needs to update the array when merge doesn't work because it just appends to the end of an array instead of providing the ability to change an existing value.
Let's say you have an array words_array like below:
Object {
0: "First word"
1: "Second word"
2: "Third word"
}
In order to update "Second word", you can do the following:
{% set words_array = {(1): 'New word'} + words_array %}
The resulting array would be:
Object {
0: "First word"
1: "New word"
2: "Third word"
}
You can take it one step further if you are using a loop and use the loop.index0 variable something like the following:
{% for word in words_array %}
{% if word == 'Second word' %}
{% set words_array = {(loop.index0): 'New word'} + words_array %}
{% endif %}
{% endfor %}
You can declare the array as follows
{% set arr = [{'element1': 'value1','element2' : 'value2'},{'element1': 'value1','element2' : 'value2'},{'element1': 'value1','element2' : 'value2'}] %}
I had a multi dimension array. The only solution I could find out is create a new temporary array and update/add the information, which was further passed on to another twig function.
I had this problem sometime ago. Imagine you have an array like this one:
data = {
'user': 'admin',
'password': 'admin1234',
'role': 'admin',
'group': 'root',
'profile': 'admin',
'control': 'all',
'level': 1,
'session': '#DFSFASADASD02',
'pre_oa': 'PRE-OA',
'hepa_oa': 'HEPA-OA',
'pre_ra': 'HEPA-RA',
'hepa_ra': 'HEPA-RA',
'deodor_ra': 'DEODOR-RA'
}
So, you want to show this data in two rows, but remove the password from that list. To this end, split in 2 arrays will be easy with the slice filter. However, we have to remove the password. For that reason, I'm using this snippet. The idea is to put all the elements lesser than the data elements size divided by 2. To calculate this we use the filter length. Now to get the index of the current element we user loop.index. And finally we *push an associative element in the left or right array. An associative array has two components key and value. To reference an array key in twit we operator () and we use the merge filter to push into the array as shown here {% set left_list = left_list|merge({ (key): value }) %}
This is the complete solution.
{% set left_list = {} %}
{% set right_list = {} %}
{% set limit = data|length // 2 %}
{% for key, value in data|cast_to_array %}
{% if key != 'password' %}
{% if loop.index <= limit %}
{% set left_list = left_list|merge({ (key): value }) %}
{% else %}
{% set right_list = right_list|merge({ (key): value }) %}
{% endif %}
{% endif %}
{% endfor %}
{% for key, value in left_list %}
<p>
<label for="{{key}}">{{key}}</label>
<input type="text" name="{{key}}" id="{{key}}" value="{{value}}"
class="text ui-widget-content ui-corner-all">
</p>
{% endfor %}

Categories