Phalconphp FW custom volt function and inserting variables into the functions - php

I have a problem with inserting dynamic variables into custom volt functions.
For example i have a code:
{{ myFunction(variableFromController) }}
And accessing the variable:
$compiler->addFunction('myFunction',function($resolvedArgs,$exprArgs)use($di,$compiler){
$arg = $compiler->expression($exprArgs[0]['expr']);
$lang = $di->get('lang');
echo $lang->translate($arg);
});
So problem is that compiler returns a variable as string "$variableFromController", so i cannot acccess content from variable.
Do you know how to pass variable correct into custom function or is a problem in accessing ?
Thanks.

I think that there is a misconception of what Volt's function is. It looks like it is. Your Volt function should return a string which can be then evaluated by PHP. As an example from Phalcon's docs:
$compiler->addFunction('widget', function($resolvedArgs, $exprArgs) {
return 'MyLibrary\Widgets::get(' . $resolvedArgs . ')';
});
So based on that I think that your function should return (but I haven't tested this):
$compiler->addFunction('myFunction',function($resolvedArgs,$exprArgs)use($di,$compiler){
$arg = $compiler->expression($exprArgs[0]['expr']);
return '$this->lang->translate(' . $arg . ');';
});
This is because Volt acts as a compiler of Volt (Twig) syntax to PHP templates (you can check this by examine volt's output folder. So it output PHP files that are later used to render views.
I've found that adding helper object to di container is better for complex operations. I can add a helper to di container end us it as below:
Provided the code from above you could skip all the myFunction thing and just use lang from di container:
{{ lang.translate(variable) }}
since you can directly access all services from di in view.

Related

Can twig macros return values?

I'm trying to write a template in Twig. Inside it I would like it to perform some manipulations on the string data that comes from the controller. In particular, there's a common manipulation (convert from underscore_case to CamelCase) that I'd like to bring out into a separate function. Then later I can use it repeatedly like {% set x = magic(a) ~ magic(b) %}. However I cannot find how to make such a reusable function inside the template itself. There are macros, but those don't seem to be able to return values. Filters are another option that seem to fit the bill, but I can only define those on PHP side.
Can this be done? Or should I do all the advanced string manipulation controller-side? It kinda feels like I'm pulling parts of display logic in there; things that should be in the view.
Twig is for outputting data. If you need to "transform" the data you need to do that before you send it to twig or you need to extend twig
Ideally, all the data you send to twig is just variables and arrays that needs the least amount of manipulation on their own.
When you're actually "in" twig, the data processing can be assumed to be "done" and only needs to be outputted in the appropriate places with minimal logic to decide user interface styles.
So revisit your logic and prepare your data better before sending it to twig.
An example for extending a toolkit class that contains our magic methods to do real wizardry.
class CustomToolkit
{
public function magic_a($a)
{
return strtolower($a); }
public function magic_b($b)
{
return camel_case($b);
}
public function magic_tidle($a, $b)
{
return $this->magic_a($a) ~ $this->magic_b($b);
}
}
Then you add this to your twig instance. I added here a complete instantiation loop. if you have a service provider you can just grab the instance from there and add it to that one.
$twig = new Twig_Environment(new Twig_Loader_Array([
'html' => $contents
]),[
'auto_reload' => true,
'debug' => false,
]);
$twig->addExtension('toolkit', new CustomToolkit ());
echo $twig->render('html', $values);
Then in your twig code you should be able to do something along the lines of
{% set x = toolkit.magic_tidle("value","value_b") %}
You are right, macros do not have return values and you cannot really make them have any. All they do is outputting strings.
Still, you are able to capture string output using set: https://twig.symfony.com/doc/2.x/tags/set.html
The syntax looks similar to this:
{% set var %}
{{ call.macro() }}
{% endset %}
The output of the macro call is then stored inside var. You may want to strip the whitespace though.
But then, consider rethinking what you are doing. Is this still presentation logic, or is your controller simply "too lazy" to transform the strings prior to passing them to twig? If it's really presentation logic, simply adding a twig filter by extending twig surely is worth the hassle. Not only because your code becomes testable.
Can this be done?
Yes! However, Twig templates are not the ideal places to run logic. At least, we should do our best to avoid it. Instead, Controller should return what Twig template needs. Logic should run in Service, Utility, Helper (you name it) etc. and Controller returns it to Twig. Twig then just displays it.
Can twig macros return values?
Yes! Look at this example. It accepts parameters and returns (not a real "return" thing but you get the idea) something back.
Example:
Assuming that the data you are trying to manipulate is something simple.
use Doctrine\Common\Inflector\Inflector;
Controller
{
action() {
$data = Inflector::camelize('hello_world'); // This will become helloWorld
return ....;
}
}
Look into Inflector class. It has useful stuff.

Call ViewHelper from Controller

i`m trying to call a ViewHelper Function from within a Controller in Typo3 (to add some additional header data)
Base is the Yag Gallery.
I edited the ItemListController.php and added the following:
$pager = $this->extListContext->getPagerCollection();
$prevLinkUid = $pager->getPreviousPage();
$arg = Tx_YAG_ViewHelpers_Namespace_GPArrayViewHelper::render([page:$prevLinkUid], $pager)
$test = '<link rel="test" href="' . $arg . '">';
$this->response->addAdditionalHeaderData($test);
The addAdditionalHeaderData function works well with other data (e.g. $prevLinkUid, so this part is functioning well).
If i understand the syntax of GPArrayViewHelper::render correctly, i need a pageUid as first argument and the pagerCollection as second argument (derived from this call within Resources/Private/Partials/Pager/Default.html
<extlist:link.action controller="{controller}" action="{action}" arguments="{extlist:namespace.GPArray(object:'{pagerCollection}' arguments:'page:{pager.previousPage}')}"><span><</span> </extlist:link.action>
)
However - if i try this Controller my page won't render, so i assume there is something wrong with the php code / function call, maybe even the syntax of the key value pair/first argument? Sorry, i'm not a professional in php
Any ideas how can i achieve this? I read that it may be difficult to use ViewHelpers from within other controllers?
there is a ViewHelperInvoker class which you can use to render a viewhelper in a controller:
$viewHelperInvoker = GeneralUtility::makeInstance(\TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInvoker::class);
$result = $viewHelperInvoker->invoke(
\TYPO3\CMS\Fluid\ViewHelpers\Format\CurrencyViewHelper::class,
[ 'currencySign' => '€' ],
new \TYPO3\CMS\Fluid\Core\Rendering\RenderingContext(),
function() {
return 12345.67;
}
);
https://github.com/TYPO3/Fluid/blob/master/src/Core/ViewHelper/ViewHelperInvoker.php
Viewhelpers are not supposed to be used outside of Fluid templates.
However, there might be an easier way to achieve what you want, e.g. with HeaderAssets. This way you can easily add snippets to the <head> of your page from within a controller action or page template.
Please really don't to that. What you can do is using the PageRenderer. E.g. using
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
$pageRenderer->addHeaderData($headerData);

Laravel: call static function inside blade template

I found very strange situation. I have a collection with some results and I wanna grab for each of that results the saved in cache information. For that I have a Cache class, which has one static function get(). Unfortunately I am not receiving anything when I call it in the foreach loop of my Blade template.
#foreach($prognoses as $sport_prognose)
<?php
$pr = Cache::get(Config::get('variables.cache.prediction'), ['id' => $sport_prognose['id']]);
print_r($pr);
die();
?>
#endforeach
If I call the same function inside the Controller is display me the needed information, but not as in the example above.
Why is that ?
You can use cache() and config() and other global helpers instead of facades to avoid this kind of problem.
Inside the blade template, you can write something like this:
{{ $pc::getProducts($ship->products) }}
Notice the use of variables. Obviously getProducts is a static method inside the controller, and $ship->products is a variable coming from an array. Let's make it simple: suppose $ship->products is 1, and getProducts is this:
static function getProducts($id) { echo "id is $id; }
If you ran this script, you'd get an error because the template lacks the value of $pc. How do you get around this? You need to pass the value of $this to the template:
return View::make('shipping.index')->with(['pc' => $this, 'shipping' => $shippings);
Here shipping.index is the template, and pc is getting a value of $this, which allows $pc to get accceess to getProducts inside the blade template.

Evaluate twig functions in record

I've created a bolt extension which provides a new twig function foo. The twig functions is added to the twig framework with the following code $this->addTwigFunction('foo', 'twigFoo');.
public function twigFoo()
{
$markup = '
<hr>
Foo
<hr>';
return new \Twig_Markup($markup, 'UTF-8');
}
My idea was that the users of the cms can use the twig function in the content types. But when the body of a record is displayed the twig function is visible as plain HTML for example: {{ foo }}
I think the problem is, that the twig template will be rendered before the record body will be assigned. So the body of my record will not be evaluated by twig. Has anyone a idea how to evaluate the twig function witch is use in a record? What's the best practice for this problems?
The field in the ContentType needs allowtwig: true to tell Bolt that you trust the field/editor to allow this, e.g.:
body:
type: html
allowtwig: true
The Problem is that Twig does not render Twig inside a Twig variable. You could create an escape function to still do that. Anyway this might not be the best idea to give your CMS users the possibility to use Twig as this gives them full access to your code.
Anyway, here is an escape function that could help you
$this->app['twig']->getExtension('core')->setEscaper('code', function($twigEnv, $string, $charset) {
$twig = clone $this->app['twig'];
$twig->setLoader(new \Twig_Loader_String());
return $twig->render($string);
});
You could then use the twig filter "code" in your template. e.g.:
{{ record.body|escape('code') }}

How to Set Variables in a Laravel Blade Template

I'm reading the Laravel Blade documentation and I can't figure out how to assign variables inside a template for use later. I can't do {{ $old_section = "whatever" }} because that will echo "whatever" and I don't want that.
I understand that I can do <?php $old_section = "whatever"; ?>, but that's not elegant.
Is there a better, elegant way to do that in a Blade template?
EASY WAY
If you want to define multiple variables, use the full form of the blade directive:
#php
$i = 1;
$j = 2;
#endphp
If you only want to define one variable, you can also use a single PHP statement:
#php($i = 1)
MORE ADVANCED: ADD A 'DEFINE' TAG
If you want to use custom tags and use a #define instead of #php, extend Blade like this:
/*
|--------------------------------------------------------------------------
| Extend blade so we can define a variable
| <code>
| #define $variable = "whatever"
| </code>
|--------------------------------------------------------------------------
*/
\Blade::extend(function($value) {
return preg_replace('/\#define(.+)/', '<?php ${1}; ?>', $value);
});
Then do one of the following:
Quick solution: If you are lazy, just put the code in the boot() function of the AppServiceProvider.php.
Nicer solution:
Create an own service provider. See https://stackoverflow.com/a/28641054/2169147 on how to extend blade in Laravel 5. It's a bit more work this way, but a good exercise on how to use Providers :)
After the above changes, you can use:
#define $i = 1
to define a variable.
It is discouraged to do in a view so there is no blade tag for it.
If you do want to do this in your blade view, you can either just open a php tag as you wrote it or register a new blade tag. Just an example:
<?php
/**
* <code>
* {? $old_section = "whatever" ?}
* </code>
*/
Blade::extend(function($value) {
return preg_replace('/\{\?(.+)\?\}/', '<?php ${1} ?>', $value);
});
In laravel-4, you can use the template comment syntax to define/set variables.
Comment syntax is {{-- anything here is comment --}} and it is rendered by blade engine as
<?php /* anything here is comment */ ?>
so with little trick we can use it to define variables, for example
{{-- */$i=0;/* --}}
will be rendered by bladeas
<?php /* */$i=0;/* */ ?> which sets the variable for us.
Without changing any line of code.
There is a simple workaround that doesn't require you to change any code, and it works in Laravel 4 just as well.
You just use an assignment operator (=) in the expression passed to an #if statement, instead of (for instance) an operator such as ==.
#if ($variable = 'any data, be it string, variable or OOP') #endif
Then you can use it anywhere you can use any other variable
{{ $variable }}
The only downside is your assignment will look like a mistake to someone not aware that you're doing this as a workaround.
Ya'll are making it too complicated.
Just use plain php
<?php $i = 1; ?>
{{$i}}
donesies.
(or https://github.com/alexdover/blade-set looks pretty straighforward too)
We're all kinda "hacking" the system by setting variables in views, so why make the "hack" more complicated then it needs to be?
Tested in Laravel 4.
Another benefit is that syntax highlighting works properly (I was using comment hack before and it was awful to read)
Since Laravel 5.2.23, you have the #php Blade directive, which you can use inline or as block statement:
#php($old_section = "whatever")
or
#php
$old_section = "whatever"
#endphp
You Can Set Variables In The Blade Templating Engine The Following Ways:
1. General PHP Block
Setting Variable: <?php $hello = "Hello World!"; ?>
Output: {{$hello}}
2. Blade PHP Block
Setting Variable: #php $hello = "Hello World!"; #endphp
Output: {{$hello}}
You can set a variable in the view file, but it will be printed just as you set it. Anyway, there is a workaround. You can set the variable inside an unused section. Example:
#section('someSection')
{{ $yourVar = 'Your value' }}
#endsection
Then {{ $yourVar }} will print Your value anywhere you want it to, but you don't get the output when you save the variable.
EDIT: naming the section is required otherwise an exception will be thrown.
In laravel document https://laravel.com/docs/5.8/blade#php
You can do this way:
#php
$my_variable = 123;
#endphp
In Laravel 4:
If you wanted the variable accessible in all your views, not just your template, View::share is a great method (more info on this blog).
Just add the following in app/controllers/BaseController.php
class BaseController extends Controller
{
public function __construct()
{
// Share a var with all views
View::share('myvar', 'some value');
}
}
and now $myvar will be available to all your views -- including your template.
I used this to set environment specific asset URLs for my images.
Laravel 7 :
{{ $solution = "Laravel 7 is awesome and easy to use !!" }}
And suddenly nothing will appear.
From my experience, if you have to do something like this prepare the html in a model's method or do some reorganizing of your code in to arrays or something.
There is never just 1 way.
{{ $x = 1 ? '' : '' }}
In Laravel 5.1, 5.2:
https://laravel.com/docs/5.2/views#sharing-data-with-all-views
You may need to share a piece of data with all views that are rendered by your application. You may do so using the view factory's share method. Typically, you should place calls to share within a service provider's boot method. You are free to add them to the AppServiceProvider or generate a separate service provider to house them.
Edit file: /app/Providers/AppServiceProvider.php
<?php
namespace App\Providers;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
view()->share('key', 'value');
}
public function register()
{
// ...
}
}
I'm going to extend the answer given by #Pim.
Add this to the boot method of your AppServiceProvider
<?php
/*
|--------------------------------------------------------------------------
| Extend blade so we can define a variable
| <code>
| #set(name, value)
| </code>
|--------------------------------------------------------------------------
*/
Blade::directive('set', function($expression) {
list($name, $val) = explode(',', $expression);
return "<?php {$name} = {$val}; ?>";
});
This way you don't expose the ability to write any php expression.
You can use this directive like:
#set($var, 10)
#set($var2, 'some string')
You may use the package I have published: https://github.com/sineld/bladeset
Then you easily set your variable:
#set('myVariable', $existing_variable)
// or
#set("myVariable", "Hello, World!")
As for my elegant way is like the following
{{ ''; $old_section = "whatever"; }}
And just echo your $old_section variable.
{{ $old_section }}
If you have PHP 7.0:
The simple and most effective way is with assignment inside brackets.
The rule is simple: Do you use your variable more than once? Then declare it the first time it's used within brackets, keep calm and carry on.
#if(($users = User::all())->count())
#foreach($users as $user)
{{ $user->name }}
#endforeach
#else
There are no users.
#endif
And yes, I know about #forelse, this is just a demo.
Since your variables are now declared as and when they are used, there is no need for any blade workarounds.
Assign variable to the blade template, Here are the solutions
We can use <?php ?> tag in blade page
<?php $var = 'test'; ?>
{{ $var }
OR
We can use the blade comment with special syntax
{{--*/ $var = 'test' /*--}}
{{ $var }}
I also struggled with this same issue. But I was able to manage this problem by using following code segment. Use this in your blade template.
<input type="hidden" value="{{$old_section = "whatever" }}">
{{$old_section }}
I don't think that you can - but then again, this kind of logic should probably be handled in your controller and passed into the view already set.
I was looking for a way to assign a value to a key and use it many times in my view. For this case, you can use #section{"key", "value"} in the first place and then call #yield{"key"} to output the value in other places in your view or its child.
In laravel8
#php
$name="Abdul mateen";
{{ echo $name; }}
#endphp
Hacking comments is not a very readable way to do it. Also editors will color it as a comment and someone may miss it when looking through the code.
Try something like this:
{{ ''; $hello = 'world' }}
It will compile into:
<?php echo ''; $hello = 'world'; ?>
...and do the assignment and not echo anything.
It's better to practice to define variable in Controller and then pass to view using compact() or ->with() method.
Otherwise #TLGreg gave best answer.
There is a very good extention for Blade radic/blade-extensions. After you add it you can use #set(variable_name, variable_value)
#set(var, 33)
{{$var}}
In my opinion it would be better to keep the logic in the controller and pass it to the view to use. This can be done one of two ways using the 'View::make' method. I am currently using Laravel 3 but I am pretty sure that it is the same way in Laravel 4.
public function action_hello($userName)
{
return View::make('hello')->with('name', $userName);
}
or
public function action_hello($first, $last)
{
$data = array(
'forename' => $first,
'surname' => $last
);
return View::make('hello', $data);
}
The 'with' method is chainable. You would then use the above like so:
<p>Hello {{$name}}</p>
More information here:
http://three.laravel.com/docs/views
http://codehappy.daylerees.com/using-controllers
I had a similar question and found what I think to be the correct solution with View Composers
View Composers allow you to set variables every time a certain view is called, and they can be specific views, or entire view templates. Anyway, I know it's not a direct answer to the question (and 2 years too late) but it seems like a more graceful solution than setting variables within a view with blade.
View::composer(array('AdminViewPath', 'LoginView/subview'), function($view) {
$view->with(array('bodyClass' => 'admin'));
});
laravel 5 you can easily do this . see below
{{--*/ #$variable_name = 'value' /*--}}
You can extend blade by using the extend method as shown below..
Blade::extend(function($value) {
return preg_replace('/\#var(.+)/', '<?php ${1}; ?>', $value);
});
after that initialize variables as follows.
#var $var = "var"
inside the blade file, you can use this format
#php
$i++
#endphp

Categories