How to read complex variables? - php

<?php
class config {
public static function get($path = null) {
if ($path) {
$config = $GLOBALS['config'];
$path = explode('/', $path);
foreach($path as $bit) {
if (isset($config[$bit])) {
$config = $config[$bit];
}
}
return $config;
}
return false;
}
}
?>
I'am following this tutorial http://www.youtube.com/watch?v=S6vDgLwJ7n8&list=PLfdtiltiRHWF5Rhuk7k4UAU1_yLAZzhWc from phpacademy. I got lost and can't understand this lines
function get ($path = null) ---- what does this mean? from my understanding it says "get the value of $path". but it leads me to another question, where can I get the value of $path? please enlighten me and translate this statement in english.
if ($path) ----- does this check if $path has a value?
$config = $config[$bit] ---- this is my first time to encounter this, I can't understand because there is a bracket with a variable in it. please enlighten me and teach me how can I translate this and read it in plain english.

function get($path = null)
That line declares a function named get which will accept one parameter named path. It has also provided a default value for path which is null. In plain that means if no path is sent to this function, then consider the path to be a null value.
This can be better explained with a different example
function display($message="Hello World")
{
echo $message;
}
If this function is called like
display("Testing"); // It will output `Testing`
display(); // It will output `Hello World`
Secondly
$config[$bit];
Means the value in $config array which has an index which is stored in $bit variable.
For example
$a=array();
$a["test"]=1;
$index="test";
echo $a["test"]; //echoes 1
echo $a[$index]; //echoes 1

function get($path = null)
This is a function declaration where the default value for $path is already set. Nothing more or less. The default value is NULL which means ... well, nothing. You can still hand over an argument and overwrite the default.

Related

Creating a translation class with PHP associative array

I have written a very simple translation class that is supposed to return the meaning associated with the phrase that I give to it. Under the hood, it loads translations from a csv upon construction into an associative array. Upon translation request, it checks the array. If the phrase is there as a key in the array, returns its value, which is its translation. If the phrase does not exist as a key, it loads the array from the file again (as there might be new translations), checks for the key again. If it does not find the key again, the phrase will be returned as is.
<?php
class Translate{
function __construct() {
$this->loadTranslations();
}
public function get($message, $lang = "de"): string{
if(key_exists($message, self::$de)){
return self::$de[$message];
}
else {
//Load translations again
$this->loadTranslations();
if(isset(self::$de[$message])){
return self::$de[$message];
}
else {
return $message;
}
}
}
protected static $de = [];
protected function loadTranslations() {
$file = fopen(__DIR__ . "/../data/de.csv", "r");
if($file){
while($line = fgets($file)){
$en_de = explode(":", $line);
self::$de[array_shift($en_de)] = array_shift($en_de);
}
}
fclose($file);
}
}
$t = new Translate();
echo $t->get("Hello") . PHP_EOL;
Content of de.csv is like this:
"Hi": "Hallo"
"Hello": "Hallo"
The problem is when asked for a translation, the class always returns the given phrase. When I dump the array, the phrase is there as a key, but there is no success in accessing $array[$phrase] as PHP does not find the key in the array!
The problem is that in your CSV file, you have quotes round the text, so although Hello exists, it's actually stored in the translation array as "Hello" so will not match.
You could either redo your translation file to not have the quotes, or you could use the functionality of fgetcsv() to read it and strip out any surrounding quotes (use : as the separator)...
protected function loadTranslations() {
$file = fopen(__DIR__ . "/a.csv", "r");
if($file){
while([$key, $trans] = fgetcsv($file, null, ":", '"')){
self::$de[$key] = $trans;
}
}
fclose($file);
}
Just looking at the code to fetch the translation, you could shorten it. First check that the translations are loaded, then return the translation - using ?? to say if it's not found, then return the original message...
public function get($message, $lang = "de"): string{
if(!isset(self::$de)){
$this->loadTranslations();
}
return self::$de[$message] ?? $message;
}
Your csv looks more like json to me.
I'd probably adjust the file to be json permanently, but until then, just convert it into a json string manually, then decode it to create your key-value pairs.
self::$de = json_decode(
'{' . implode(',', file(__DIR__ . "/a.csv")) . '}',
true
);
In other words, make all of your language files valid json. This way you can instantly cal json_decode() on the entire file contents and the array is ready. Keeping your file in the current format means individually isolating each line of text in the file and calling a function to parse it -- this is waaaaay too much work to be done each time.
Please consistently write your class variables at the top of your class.
$de should not be a variable name -- I am assuming it is referring to a specific language. $lang() should be used to specify the user's desired language and search for the appropriate filename.
Edit:
I really can't overstate how beneficial it is to convert your files to valid json -- it just makes everything cleaner. Here is a re-write of your code. I don't agree with the use of a static class variable, nor the constructor that that loads a language without know what is going to be used. And as previously mentioned there should be no variable that refers to a specific language ($de). The class variable $translations should be an associative array containing subarrays so that you can permanently load and access multiple translations at the same time.
Untested suggestion:
class Translate{
protected $translations = [];
protected function loadTranslations($lang)
{
$filePath = __DIR__ . '/' . $lang . '.json';
if (file_exists($filePath)) {
$this->translations[$lang] = json_decode(file_get_contents($filePath), true);
}
}
public function get($message, $lang = "de"): string
{
if (!isset($this->translations[$lang])) {
$this->loadTranslations($lang);
}
return $this->translations[$lang][$message] ?? $message;
}
// e.g. $newTrans = ['Good Day' => 'Guten Tag', ...]
public function set($lang, $newTrans)
{
if (!isset($this->translations[$lang])) {
$this->loadTranslations($lang);
}
$this->translations[$lang] += $newTrans; // insert or overwrite key-value pair(s)
file_put_contents(__DIR__ . '/' . $lang . '.json', json_encode($this->translations[$lang])); // commit to file
}
}
$t = new Translate();
echo $t->get("Hello") . PHP_EOL;

How to write that in PHP 7.2?

I have this piece of code:
private function _resolveCustomEntries($fields)
{
foreach ($fields as &$value) {
if (array_key_exists('custom', $value)) {
if (method_exists($this, $value['custom'])) {
$value['custom'] = $this->$value['custom'](); //variableInterpolation
}
}
}
return $fields;
}
I ran a PHP 7.2 compatibility check and it complained here with the "variableInterpolation" on the marked line. When I run this code, the PHP log tells me this:
ERR (3): Notice: Array to string conversion in
/public_html/lib/KiTT/Payment/Widget.php on line 217
Thats the same line where the "variableInterpolation" check failed. So how would I rewrite this code so it works in PHP 7.2?
Thanks!
Solution:
$value['custom'] = $this->$value['custom']();
has to look like this:
$value['custom'] = $this->{$value['custom']}();
It's a matter of order variables are evaled.
With
class x {
public function y() {
echo 'ok';
}
}
$x = new x();
$y = array('i' => 'y');
Then
$x->$y['i']();
Fails because PHP first tries to cast the $y variable into a string, and get the matching property of $x (which btw does not exist), then tries to get the index 'i' or that unexisting property, and then tries to run it as a callable.
Hence 3 errors:
Array to string conversion
Undefined property x::$Array
Function name must be a string (nda: the undefined property returns NULL)
Instead, curly brace the variable to set the resolving order:
$x->$y['i']();
Will work. So use $this->{$value['custom']}()
This will throw an array to string conversion in 7.2
class bob{
function foo(){
return 'bar';
}
function getFoo(){
$value['custom'] = 'foo';
$value['custom'] = $this->$value['custom']();
return $value['custom'];
}
}
$bob = new Bob();
var_dump($bob->getFoo());
But it will execute just fine in 5.6.
Then i changed the snippet to this, not calling the method directly casting the array key to function name, but initializing a string (hopefully, there is no type validation in your code) variable with the function name first:
class bob{
function foo(){
return 'bar';
}
function getFoo(){
$value['custom'] = 'foo';
$functionName = $value['custom'];
$value['custom'] = $this->$functionName();
return $value['custom'];
}
}
$bob = new Bob();
var_dump($bob->getFoo());
This will run just fine in php 7.2
You could try rewriting your code using complex (curly) syntax, you can read more about it here.
Your code would look something like this.
$value['custom'] = $this->{$value['custom']}();
Edit: moved the curly braces to correct positions.

Undefined Index in __isset

I am getting an error in my actual code inside __isset, but when I went to a 'run php online website' it works. So I am not sure why it does not work in my code.
<?
class Test {
private $args;
public function __construct($args = array()) {
$this->args = $args;
}
public function __isset($name) {
return $this->args[$name]; //undefined index on my server (is fine on php site)
}
function prnt() {
// echo isset($this->i) ? "set" : "notset"; --> works, both print 'set'
echo isset($this->h) ? "set" : "notset";
}
}
?>
I then execute this:
$test = new Test(array('i' => '1234'));
$test->prnt();
//result on php website: notset
//result on my website: Undefined index at the line shown above.
Possibly helpful information:
My server is running php 5.1.
The isset($this->var) is happening in an include file in my actual code.
As long as the variable exists (like i above) it obviously works.
Your error reporting settings in each environment are different. One environment is allowing errors of E_NOTICE level through while another is blocking them.
You should do something like this:
return array_key_exists($name, $this->args);
You're trying to return the value of a non existent key, instead return the result of testing for the key using array_key_exists
public function __isset($name) {
return array_key_exists($name, $this->args);
}

Issue with assigning class variable in PHP

I am trying to assign a variable to a class in PHP, however I am not getting any results?
Can anyone offer any assistance? The code is provided below. I am trying to echo the URL as shown below, by first assigning it to a class variable.
class PageClass {
var $absolute_path = NULL;
function get_absolute_path(){
$url = $this->absolute_path;
echo $url;
}
}
$page = new PageClass();
$page->absolute_path = "http://localhost:8888/smile2/organic/";
$page->get_absolute_path(); //this should echo the URL as defined above - but does not
It also works for me.
Take a look at a live example of your code here.
However, there are a few things you should change about your class.
First, Garvey does make a good point that you should not be using var. That's the older PHP4, less OOP conscious version. Rather declare each variable public or private. In fact, you should declare each function public or private too.
Generally, most classes have private variables, since you usually only want to change the variables in specific ways. To achieve this control you usually set several public methods to allow client functions to interact with your class only in restricted predetermined ways.
If you have a getter, you'd probably want a setter, since these are usually used with private variables, like I described above.
A final note is that functions named get usually return a value. If you want to display a value, it is customary to use a name like display_path or show_path:
<?php
class PageClass
{
private $absolute_path = NULL;
public function set_absolute_path($path)
{
$this->absolute_path = $path;
}
public function display_absolute_path()
{
echo $this->absolute_path;
}
}
$page = new PageClass();
$page->set_absolute_path("http://localhost:8888/smile2/organic/");
$page->display_absolute_path();
// The above outputs: http://localhost:8888/smile2/organic/
// Your variable is now safe from meddling.
// This:
// echo $this->absolute_path;
// Will not work. It will create an error like:
// Fatal error: Cannot access private property PageClass::$absolute_path on ...
?>
Live Example Here
There's a section on classes and objects in the online PHP reference.
class PageClass {
public $absolute_path = NULL;
function get_absolute_path(){
$url = $this->absolute_path;
return $url;
}
}
$page = new PageClass();
$page->absolute_path = "http://localhost:8888/smile2/organic/";
echo $page->get_absolute_path();
Works fine for me.
Have you checked that the script and esp. the code in question is executed at all?
E.g. add some unconditional debug-output to the script. Or install a debugger like XDebug to step through the code and inspect variables.
<?php
class PageClass {
var $absolute_path = NULL; // old php4 declaration, see http://docs.php.net/oop5
function get_absolute_path() { // again old php4 declaration
$url = $this->absolute_path;
echo "debug: "; var_dump($url);
echo $url;
}
}
$page = new PageClass();
$page->absolute_path = "http://localhost:8888/smile2/organic/";
echo "debug: page->get_absolute_path\n";
$page->get_absolute_path();

File private variables in PHP

Is it possible to define private variables in a PHP script so these variables are only visible in this single PHP script and nowhere else? I want to have an include file which does something without polluting the global namespace. It must work with PHP 5.2 so PHP namespaces are not an option. And no OOP is used here so I'm not searching for private class members. I'm searching for "somewhat-global" variables which are global in the current script but nowhere else.
In C I could do it with the static keyword but is there something similar in PHP?
Here is a short example of a "common.php" script:
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// Do more stuff with the $dir variable
When I include this file in some script then the $dir variable is visible in all other scripts as well and I don't want that. So how can I prevent this?
There are a few things you could do to keep $dir out of subsequent files
Example 1
set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path());
This is the most obvious.
Example 2
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// work with $dir
unset($dir);
Just unset the variable after defining it and using it. Note this will unset any variable named $dir used prior to including this script.
Example 3
define('DIR_THIS', dirname(__FILE__));
set_include_path(DIR_THIS . PATH_SEPARATOR . get_include_path());
It is less likely I suppose to redefine a global constant like this.
Example 4
function my_set_include_path {
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// Do more stuff with the $dir variable
$my_other_var = 'is trapped within this function';
}
my_set_include_path();
You can define as many variables within that function and not affect the global namespace.
Conclusion
The first method is the easiest way to solve this problem, however because you want to use $dir again, it may not be ideal. The last example will at least keep that $dir (and any others defined in that function) out of the global namespace.
The only way you're going to accomplish anything close to what you want is to wrap everything in that included file in a function, and call it. If the file needs to execute itself you could still do
<?php
run_myfile()
function run_myfile() {
...
}
?>
There is no generic way to make a variable scoped to only a file outside of namespaces, classes, or functions.
Well, I'm probably getting flailed for this, but you if you are totally desperate you could use a Registry for that. I've whipped up a small one that does without classes (since I assume from And no OOP is used here so I'm not searching for private class members. means you don't want to do it with OOP at all)
function &registry_get_instance()
{
static $data = array();
return $data;
}
The static $data variable inside is persisted inside the function scope, so you can call the function wherever you like and always get the same contents. The crucial point is returning by reference, e.g.
$registry = &registry_get_instance(); // get $data array by reference
$registry['foo'] = 'bar'; // set something to $data
unset($registry); // delete global reference to $data
print_r(&registry_get_instance()); // show $data
Obviously you'd still have $registry as a variable in the global scope when calling this method from the global scope. So, you could add some more functions to make the Registry more convenient to use, e.g. for setting data to the Registry:
function registry_set($key, $value)
{
$registry = &registry_get_instance();
$registry[$key] = $value;
}
and for getting it out again:
function registry_get($key)
{
$registry = &registry_get_instance();
if(array_key_exists($key, $registry)) {
return $registry[$key];
} else {
trigger_error(sprintf(
'Undefined Index: %s', htmlentities($key)
), E_USER_NOTICE);
}
}
and for checking if a key exists:
function registry_isset($key)
{
$registry = &registry_get_instance();
return array_key_exists($key, $registry);
}
which you could then use like:
registry_set('foo', 'bar'); // setting something to the registry
var_dump( registry_isset('foo') ); // check foo is in the registry now
echo registry_get('foo'); // prints 'bar'
echo registry_get('punt'); // raises Notice
You could populate the Registry from an include file with an additional method like this:
function registry_load_file($file)
{
if(!is_readable(realpath($file))) {
return trigger_error(sprintf(
'File is not readable: %s', htmlentities($file)
), E_USER_WARNING);
}
$config = include $file;
if(!is_array($config)) {
return trigger_error(sprintf(
'Expected file %s to return an array', htmlentities($file))
, E_USER_WARNING);
}
$registry = &registry_get_instance();
$registry += $config;
}
with the include file having to return an array:
// config.php
return array(
'setting1' => 'something'
);
and then you can do
registry_load_from_file('config.php'); // add the contents of config to registry
print_r(registry_get_instance()); // show content of registry
Of course, this is now six functions in the global scope just for not having a global variable. Don't know if it's worth it, especially since I consider static in functions and all that reference stuff doubtful practice.
Take it as a proof of concept :)
Why not just put everything in a static class? Then you only have a single "variable" that could possibly conflict with the global namespace.
class MyClass {
public static $myvar = 1;
public static $myvar2 = "xyz";
public static function myfunction() {
self::$myvar++;
self::$myvar2 = "abc";
}
}
// References to class items, if needed
MyClass::myfunction();
MyClass::$myvar += 3;
If the problem you are trying to is just:
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// Do more stuff with the $dir variable
Then the solution would be to change the include path relative to '.' in your ini settings. E.g. change:
include_path=includes:/usr/local/php
to
include_path=./includes:/usr/local/php
Note that a script does not come into scope except where you explicitly include/require it (both the _once check applies globally) however I would recommend strongly against calling include/require from within a function - its much more transparent having the includes/requires at the top of the script.
I think that the problem you are trying to solve is based on a false premise and you should look for another way of fixing it. If you want the code in an include file to behave differently depending on what includes it, then really you should seperate it out into 2 seperate files - or maybe even 3 - 2 for the different behaviours and 1 for the common.
C.

Categories