Hello I am fairly new to developing with PHP and Twig and have encountered a problem when updating global variables in twig. I though it would be nice to have a flash / error message global variable that I could display to a user if they have entered incorrect input (ex. a login screen).
At the moment I am using the session in php for this flash message. When the flash message is updated it should also be updated in Twig. But it does not update until I reload the page. At this point the flash message may be changed. I thought the problem may have been in PHP so I echoed the flash variable in my code before the Twig template was rendered and the flash variable was updated in that respective echo statement. I want to stress that Twig does update the flash message, but it does not until the page is loaded again. So it never has the current flash message.
I have written a short program to demonstrate this. If the user presses button "one" then the flash message should be "message one" and if they press button "two" the flash message should be "message two". I have included both an echo of the flash message in php and the flash message in the Twig template.
index.php
<?php
session_start();
require_once '../PhotoBlog/utilities/twig/vendor/autoload.php';
$loader = new Twig_Loader_Filesystem('view');
$twig = new Twig_Environment($loader, array('cache' => false));
$twig->addGlobal('session', $_SESSION);
if (isset($_GET["test_one"])) {
$_SESSION["flash"] = "message one";
}else if(isset($_GET["test_two"])) {
$_SESSION["flash"] = "message two";
}
echo "PHP says: ".$_SESSION["flash"]."<br><br>";
echo $twig->render('index.html');
?>
index.html
<form action="index.php" method="GET">
<input type="submit" name="test_one" value="one">
<input type="submit" name="test_two" value="two">
</form>
<p>Twig says: {{ session.flash }}</p>
Ideally the messages should match each other but the Twig message always prints the previous message.
But Twig will always output the second to last submission. I can't seem to get my head around this. I have looked through the Twig docs and through stackoverflow and haven't found any solutions. I turned caching off so I think I have ruled that out but maybe I am missing something here.
Array's are passed by value by default in PHP, unless otherwise specified in the signature of the method (function addGlobal($key, $value) vs function addGlobal($key, &$value) {}).
If you really wanted to update the flash messages you'd need to switch up to an object to solve this.
<?php
class Foo {
protected $array = [];
public function addGlobal($key, $value) {
$this->array[$key] = $value;
return $this;
}
public function getGlobal($key) {
return $this->array[$key] ?? null;
}
}
class Bar {
protected $value;
public function setValue($value) {
$this->value = $value;
return $this;
}
public function getValue() {
return $this->value;
}
}
$foo = new Foo();
$bar = new Bar();
$bar->setValue('foobar');
$array = [ 'bar' => 'foobar', ];
$foo->addGlobal('array', $array);
$foo->addGlobal('object', $bar);
$array['bar'] = 'bar';
$bar->setValue('bar');
var_dump($foo->getGlobal('object')->getValue()); /** Output: bar **/
var_dump($foo->getGlobal('array')['bar']); /** Output: foobar **/
demo
Related
In my yii app, i get a date string via ajax get then i try to pass it to my feature widget. The thing is that sometimes i get the date via GET url and sometimes i use a default one that i get directly in the widget (so in that case no variable needs to be passed). Here is the code:
controller:
public function actionGetDate($date) {
$this->render('index',array('date' =>$date));
}
The $date passes to the view without any problem.
My view:
if (isset($date))
{
echo "$date !!!!";
$this->widget('FullWidget',array('date2' =>$date) );
} else {
echo "not date";
$this->widget('FullWidget');
}
when $date is not set, then the code runs without any issue.
when $date is set and then it goes to this line below and it gets me 404 Error..
$this->widget('FullWidget',array('date2' =>$date) );
here is the error:
FullWidget:
class FullWidget extends CWidget {
private $_userConfig;
public function init() {
$this->_userConfig = Yii::app()->getModule('module1234')->getDefaultConfig();
}
public function run() {
if (isset($date2))
{
echo "yes!!!";
//$this->render('small_Widget', array('config' => $this->_userConfig,'date' => $date2));// this line is commented and i dont run it yet. the problem is when i'm trying to pass the variable from the view to the FullWidget
}else{
echo "no!!!!";
$this->render('small_Widget', array('config' => $this->_userConfig));
}
}
}
Does anybody knows whats the problem?
Thanks!!
I'm making an admin setting section of my laravel 5.2 app using the storage package from thetispro/laravel5-setting.
I'd like my admin users to be able to update email copy that get sent out to the user, but some of the emails include variables such as the users name. "Thanks for shopping with us, CUSTOMER NAME".
I can easily store the following in a setting, but when blade outputs it it just prints it out as a string instead of a variable. I've tried escaped and nonescaped the characters with {{}} and {{!! !!}. Here's what I have:
Email message an admin user can edit:
<h2>Hi, {{ $user->name }}</h2>
<p>Welcome to my web app</p>
In my view I have:
{!! Setting::get('emailuserinvite') !!}
<br /><br />
<!-- Testing both escaped and nonescaped versions -->
{{ Setting::get('emailuserinvite') }}
What blade renders is just:
echo "<h2>Hi, {{ $user->name }}</h2>
<p>Welcome to my web app</p>";
I was trying to make a custom blade directive that could close the echo, display the variable and open the echo back up, but that doesn't seem to be working correctly either.
// AppServiceProvider
Blade::directive('echobreak', function ($expression) {
// echo "my string " . $var . " close string";
$var = $expression;
return "' . $var . '";
});
// Admin user settings
Hi #echobreak($user->name)
Welcome to my web app
Any advice would be appreciated! Thanks.
Update
I mocked up a simple test case using #abdou-tahiri's example but I'm still getting errors with the eval()'d code.
ErrorException in SettingController.php(26) : eval()'d code line 1: Undefined variable: user
And here is my simple controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use Blade;
class SettingController extends Controller
{
public function index() {
$user = [
"fname" => "Sam",
"lname" => "yerkes"];
$str = '{{ $user }}';
return $this->bladeCompile($str, $user);
}
private function bladeCompile($value, array $args = [])
{
$generated = \Blade::compileString($value);
ob_start() and extract($args, EXTR_SKIP);
try {
eval('?>'.$generated);
}
catch (\Exception $e) {
ob_get_clean(); throw $e;
}
$content = ob_get_clean();
return $content;
}
}
You may need to compile the string using Blade , check this helper function :
function blade_compile($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;
}
so in your view file :
{!! blade_compile(Setting::get('emailuserinvite'),compact('user')) !!}
Check this Is there any way to compile a blade template from a string?
<h2>Hi, $user->name</h2>
<p>Welcome to my web app</p>
Is this what you are trying to do?
<h2>Hi, {{$user->name}}</h2>
<p>Welcome to my web app</p>
Until now, unless I made a multilingual website (where I would use .mo & .po files), all the text would be scrambled all around the template and / or class files. Instead, I would like to store all static text in a file that is easily editable by my coworkers and clients (that rules out database storage and POedit).
I made a JSON file that stores the messages / static text like this:
{
"titles": {
"main_title": "This is the main title of the website",
"login_page_title": "Please, sing in",
"about_page_title": "About us"
},
"errors": {
"empty_required_field": "This field is required.",
"database_connection_error": "Couldn't connect to the database.",
}
}
Then I import it in the index.php file:
$messages = json_decode(file_get_contents("messages.json"));
And use it like:
echo($messages->titles->main_title);
Which has been working so far so good (although I'm uncertain that there aren't better ways to archieve this). At least in the template pages where everything is html with minimal logic.
But I'm having trouble using the strings from the JSON file inside the classes' functions. I would like to use the error messages when throwing exceptions, for example. But I'm quite reluctant about stating "global $message" in every function where it's used (feels repetitive). Also everybody says that globals are naughty.
So my questions are two:
1) Is the JSON file a good way to handle my problem? (and if not, why, and which method would be better?).
2) How could I retrieve the stored strings from inside the classes? I'm thinking something like extending the Exception class to include the error messages, but I'm unsure of how to do it.
Thanks in advance for your help.
One approach, which Laravel takes, is creating some sort of directory tree like the following:
lang/
en/
titles.php
errors.php
titles.php could contain the following:
<?php
return [
'main_title' => 'This is the main title of the website',
'login_page_title' => 'Please, sing in',
'about_page_title' => 'About us'
];
As for errors.php:
<?php
return [
'empty_required_field' => 'This field is required.',
'database_connection_error' => "Couldn't connect to the database.",
];
I don't really like the JSON approach because it's not very flexible. For one, in PHP files, you have access to any variables you may want to give it, there's comments, possibility of using functions to create some messages, etc. This is why I recommend the above method.
In order to get the messages, you would require the file in a variable, like $titles = require 'lang/en/titles.php', using it like: $titles['main_title']. This method also makes it easy to change the language if needed.
While I'm not 100% sure I understand your exception problem, you would throw an exception with the appropriate message like: throw new Exception($errors['empty_required_field']);
In the end I opted for a Singleton class that loads/includes a separate text file. Nice global scope and should be easy to adapt to other needs (multilingüal, separate language files, or whatever). As I said I'm no expert so all critique is welcome.
<?php
class CustomText {
private static $instance = null;
private static $text;
private function __clone() {}
// On construct, checks if the strings are stored in a session.
// If not, retrieves them from file and stores them in a session.
private function __construct() {
if(self::isStoredInSession() == true) {
self::$text = $_SESSION["custom_text"];
} else {
//self::$text = json_decode(file_get_contents("messages.json"),true);
self::$text = include_once("messages.php");
self::saveToSession();
}
}
// Private initialization called on every public method so I don't have to worry about it on other files.
private static function initialize() {
if(self::$instance == null) {
self::$instance = new self;
}
}
// Session management
private static function saveToSession() {
if(session_status() == PHP_SESSION_NONE) {
session_start();
}
if(!isset($_SESSION["custom_text"])) {
$_SESSION["custom_text"] = self::$text;
}
}
private static function isStoredInSession() {
if(session_status() == PHP_SESSION_NONE) {
session_start();
}
if(isset($_SESSION["custom_text"])) {
return true;
}
return false;
}
// Sample public functions
public static function getText($section,$string){
self::initialize();
if(isset(self::$text[$section][$string])) {
return self::$text[$section][$string];
} else {
return "";
}
}
public static function getError($string) {
self::initialize();
if(isset(self::$text["error"][$string])) {
return self::$text["error"][$string];
} else {
return "";
}
}
public static function getWebsiteTitle($section,$divider = " - ") {
self::initialize();
$title = "";
if(isset(self::$text["title"]["main"])) {
$title .= self::$text["title"]["main"];
}
if(isset(self::$text["title"][$section])) {
if(!empty($title)) {
$title .= $divider;
}
$title .= self::$text["title"][$section];
}
return $title;
}
}
What worries me the most is that I'm not sure that storing the data in a session is better that including a file on each page, and I have everything twice in the session variable and the class parameter.
How do I display the "assigned template variables" inside a displayajax() function (I am using prestashop 1.5.6.0).
If you go to:
sitedomain/index.php?id_product=1&controller=product you see the product
but if you go to:
sitedomain/index.php?id_product=1&controller=product&ajax=true you see a blank page
to have some output in that page I added this function in ProductController.php and it works:
public function displayAjax()
{
echo "something";
}
How can I access all the "assigned template variables" that I see usually in the debug console of prestashop...like $combinations $groups...
Thank you!
The assigned template variables can be returned via the following code:
$this->context->smarty->getTemplateVars();
or if you need a specific variable:
$this->context->smarty->getTemplateVars('combinations');
the method getTemplateVars() returns those variables, so you may dump it with the standard function:
var_dump($this->context->smarty->getTemplateVars());
you can add this at the displayAjax() method.
You can also call the debug window if the debug param is set (by default SMARTY_DEBUG) so url like
index.php?id_product=1&controller=product&ajax=1&SMARTY_DEBUG
with the following displayAjax():
public function displayAjax()
{
$this->context->smarty->display($this->context->smarty->getDebugTemplate());
}
will popup the window.
public function displayAjax()
{
$array= $this->context->smarty->tpl_vars['combinations'];
foreach($array as $k => $v)
{
//some code
}
}
I am trying to use AMF PHP to pass variables to a flash file, thus far I cannot see anything wrong with my code, but I have very little experience with creating classes, so here it goes, here is my code,
index.php:
<?php
include "amfphp/services/flashMe.php";
$session = true;
if ($session == true) {
$uid = '12345';
$thing = new flashMe;
$thing->push($uid);
} else {
//login
}
?>
flashMe.php:
<?php
class flashMe {
public function __construct() {
}
public function push($one)
{
return $one;//sends the uid to the flash file?
}
}
?>
Flash is looking for the flashMe class and the push method within that class, but I keep getting null variables in my flash file when I run it, is there something wrong with this code?
Thanx in advance!
Your index.php file is unnecessary.
Your second file is incomplete. Here is the example from the docs for their "hello world" class file:
<?php
class HelloWorld
{
function HelloWorld()
{
$this->methodTable = array
(
"say" => array
(
"access" => "remote",
"description" => "Pings back a message"
)
);
}
function say($sMessage)
{
return 'You said: ' . $sMessage;
}
}
?>
This file should be saved as "HelloWorld" matching the "class HelloWorld" you have named in the php file (you did this part right with FlashMe).
The example file in the docs for the Flash piece (in actionscript) is here:
import mx.remoting.*;
import mx.rpc.*;
import mx.remoting.debug.NetDebug;
var gatewayUrl:String = "http://localhost/flashservices/gateway.php"
NetDebug.initialize();
var _service:Service = new Service(gatewayUrl, null, 'HelloWorld', null , null);
var pc:PendingCall = _service.say("Hello world!");
pc.responder = new RelayResponder(this, "handleResult", "handleError");
function handleResult(re:ResultEvent)
{
trace('The result is: ' + re.result);
}
function handleError(fe:FaultEvent)
{
trace('There has been an error');
}
The gateway URL should go to wherever your services can be reached. I'm sure if you try a few you'll find the right one. The neat thing about amfphp is that it allows you to also test your services out before you try implementing them in the gateway (if you go to the URL in your browser).
I'm pretty new to AMFPHP as well, but I've found the docs to be extraordinarily useful. If you need more help on classes, you can find more info on the PHP docs page.
You missed the parenthesis after new flashMe
$thing = new flashMe();
$thing->push($uid);
Amfphp or Zend AMF only allow you to call public methods on a remote class that is exposed by your gateway. You example is not a class and therefore no remote method can be called. This looks more like something that you would do with an http post.
http://framework.zend.com/manual/en/zend.amf.server.html