How can I make my class variable assignment dynamic? - php

In the following code, I want to assign an $settings's key, help to a constant class variable value, $To_default. However, I am told Constant expression contains invalid operations. Is there any way I can get around this?
class EmailClass extends PluginBase {
private $To_default = 'scrollout#stackoverflow.com';
protected $settings = array(
'To'=>array(
'type'=>'text',
'label'=>'To',
'help'=>$this->To_default, // Constant expression contains invalid operations
),
);
I've tried declaring $To_default in various ways including private const $To_default, static private $To_default, etc. but none worked. $settings is not static, as you can see, so I don't understand why this is a problem.

Don't know deep technical explanation for this but I think this is because normally you initialize properties in the constructor, like so:
class EmailClass
{
private $To_default = 'scrollout#stackoverflow.com'; // not a constant, so better throw it into constructor too
protected $settings;
public function __construct() {
$this->settings = array(
'To'=>array(
'type'=>'text',
'label'=>'To',
'help'=>$this->To_default
)
);
}
}
Analogical I don't know why: 'if isset(expression)' doesn't work but it doesn't have to. There's a better solution: 'if(expression)'. This is just how we do it.

You can not specify a default value that refers to another class property ($settings is an array that tries to read $To_default).
My recommendation is that $settings can be the result of a getter method, for example getSettings, and inside of it you can build an array and return it.
By doing do this, you can also decide to override the $To_default value with a setTo method.
Here is an example:
<?php
class EmailClass extends PluginBase {
private string $toDefault = '';
/**
* #param string $toDefault
*/
public function __construct( string $toDefault = 'scrollout#stackoverflow.com' ) {
$this->toDefault = $toDefault;
}
/**
* #param string $toDefault
*/
public function setToDefault( string $toDefault ): void
{
$this->toDefault = $toDefault;
}
public function getSettings(): array
{
return [
'TO' => [
'type' => 'text',
'label' => 'To',
'help' => $this->toDefault,
]
];
}
}

Use a class constant rather than a variable for the default.
Constants don't begin with $, that's the problem you had when you tried this.
class EmailClass extends PluginBase {
private const TO_DEFAULT = 'scrollout#stackoverflow.com';
protected $settings = array(
'To'=>array(
'type'=>'text',
'label'=>'To',
'help'=>self::TO_DEFAULT, // Constant expression contains invalid operations
),
);
}

Related

PHP Using class property into another class and use the method again

I am not aware of the technical term how to call it.
I have a base class which has $content property holding entire page array elements. For some constraint, I cannot extends the base class
Base Class
/**
* This is the main class
*
* Class base
*/
class base {
/**
* Holding entire page data in nested array. Such as nav item, posts, metadata etc. with the respective key => []
* Example:
* $this->content = [
* 'head' => [ // some body elements such as meta tags, title etc],
* 'body' => [
* ]
* ]
*
* #var array
*/
public $content = [];
/* This is just for an example of array holding values */
public function some_methods() {
// various method to process the $content and return the element
$this->content = [
'head' => [ /* some body elements such as meta tags, title etc */ ],
'body' => [
'footer' => [ /* header elements */ ],
'nav' => [ /* nav items */ ],
'article' => [
'text' => [ /* text and title */],
'metadata' => [ /* metadata */]
],
'footer' => [ /* footer elements */ ]
]
];
}
public function load_navs( $navs ) {
$one = new one();
$one->hello($navs);
// OR
// some methods has loops in the `base` class
foreach ( $navs as $key => $value ) {
// whatever..
$one->hello($key, $value);
}
}
public function load_footer( $footer ) {
$two = new two();
$two->widget($footer);
}
}
What I want to do is to make some more classes to customize some logic of the base class methods. For that to get a value I need to use $content of the base class into the another class. They the methods from the newly created classes I will use in the base class methods
One
/**
* Class to customize or override base class logic
* Class one
*/
class one {
// to do some process I need to access $content from the `base` class. For example
public function hello($navs){
// here something to check from `nav`
// this method I will use in base class
if ($this->content['nav']['item']['selected']){
// do the stuff
}
}
}
Two
/**
* Class to customize or override base class logic
* Class two
*/
class two {
// to do some process I need to access $content from the `base` class. For example
public function hello($footer){
// here something to check from `footer`
// this method I will use in base class
if ($this->content['footer']['widget']['type'] == 'foo'){
// do the stuff
}
}
}
If you can't extends your class, you can use static attribute. An static attribute is an attribute link to the class and not to an instantiated object.
Look at this link for details. I copy past the important informations
Declaring class properties or methods as static makes them accessible
without needing an instantiation of the class.
So you can declare static $content in your base Class and use it like : base::$content;
class Base {
public static $content = [];
}
class One {
function foo () {
var_dump(Base::$content);
}
}
$foo = new One();
$foo->foo();
//show :
array (size=0)
empty
And by convention, your name Classe need to upperCase (Base, One, Two)
EDIT : No Static, No Extends.
/*
if you can't extends AND you can't use static, you can instancied Base object in you One Class.
Like Tobias say in this comment, if you want change the $content for both Base and One, you need pass the var via reference.
Look at this first example, the base->$content is empty before and after One work. And look at the 2nd example : I get the base content
via reference and the content change in both case.
My english is probably too poor so I suggest to you to read the doc for reference.
*/
class Base {
public $content = [];
function displayContent() {
var_dump($this->content);
}
}
class One {
function displayContent() {
$base = new Base();
$base->content = 'toto';
var_dump($base->content);
}
}
$base = new Base();
$one = new One();
$one->displayContent();
$base->displayContent();
// $one->content == 'toto'; but $base->content still empty.
Now: with reference :
class Base {
public $content = [];
function displayContent() {
var_dump($this->content);
}
}
class One {
public $content;
function displayContent() {
var_dump($this->content);
}
}
$base = new Base();
$one = new One();
$one->content = &$base->content;
$one->content = 'toto';
$one->displayContent();
$base->displayContent();
// NOW, $one->content == 'toto'; and $base->content = 'toto'
Please use below mentioned lines while inherits property of base class.
class One extends Base
class Two extends Base
Than after you can use the property of base class in child class

Formatting of Numbers with PDO

I have recently moved a large php application from using mssql_ functions to the PDO function using the mssql driver.
I wrote a simple library that allows drop in replacement. It all seems to work pretty well considering.
However one thing that is a bit annoying is default format of numbers and particularly numbers defined as money in the database.
Most of my smarty template pages previous simply output the number as it came from the database so someones balance might be show as
125.00
however since changing to PDO this is returned as
125.0000
This is a little annoying and off putting, but obviously not the end of the world.
My Question. Is there a workaround / trick / formatting Constant or method that I can use to get PDO to format values differently, or do I need to go an manually set the format for every number in every template throughout my app?
So basically, what I'd do is create models that represent a result-set for each table, and use PDO::FETCH_CLASS to load the data into instances of the corresponding class. For example:
class UserTable //extends AbstractTable <-- see below
{
protected $id = null;
protected $name = null;
protected $email = null;
protected $money = null;
}
Then add getters and setters that format/validate the data accordingly eg:
public function getMoney()
{
return sprintf('%.2f', $this->money);//check if not null first, obviously
}
Next, have an abstract class for these models, and implement the ArrayAccess interface in there. For example, using a simple mapping array:
protected $getterMap = [
'email' => 'getEmail',
'id' => 'getId',
'money' => 'getMoney',
];
Define a tailor-made map in each child, then have the abstract class use it like so:
//in abstract class AbstracTable implements ArrayAccess
public function offsetGet($offset)
{
if (!isset($this->getterMap[$offset])) {
throw new RuntimeException(
sprintf('%s not a member of %s', $offset, get_class($this));
);
}
$getter = $this->getterMap[$offset];
return $this->{$getter}();//use the getter, it formats the data!
}
Do something similar for all 4 methods in the interface, and now you can use this:
$row = $stmt->fetch(PDO::FETCH_CLASS, 'User');
$row['money'];//will call getMoney, and return the formatted number
A more complete example:
abstract class AbstractTable implements ArrayAccess
{
protected $id = null;//very likely to be defined in all tables
protected $getterMap = [
'id' => 'getId',
];
protected $setterMap = [
'id' => 'setId',
];
//force child classes to define a constructor, which sets up the getter/setter maps
abstract public function __construct();
public offsetExists($offset)
{
return isset($this->getterMap[$offset]);
//optionally, check if value if not null: isset($arr['keyWithNullVal']) returns null, too:
return isset($this->getterMap[$offset]) && $this->{$offset} !== null;
}
public offsetGet ( mixed $offset )
{
if (!isset($this->getterMap[$offset])) {
throw new RuntimeException('member does not exist');
}
$getter = $this->getterMap[$offset];
return $this->{$getter}();
}
public offsetSet($offset, $value )
{
if (!isset($this->setterMap[$offset])) {
throw new RuntimeException('Trying to set non-existing member');
}
$setter = $this->setterMap[$offset];
$this->{$setter}($value);
}
public offsetUnset ($offset)
{
//same as setter, but call:
//or just leave blank
$this->{$setter}(null);
}
}
class UserTable extends AbstractTable
{
//protected $id = null; in parent already
protected $name = null;
protected $email = null;
protected $money = null;
public function __construct()
{
$fields = [
'name' => 'etName',
'email' => 'etEmail',
'money' => 'etMoney',
];
foreach ($fields as $name => $method) {
$this->getterMap[$name] = 'g' . $method;
$this->setterMap[$name] = 's' . $method;
}
}
}
Obviously, you'll have to write the getters and setters for all the fields. Not to worry, though: most IDE's will helpfully generate the getters and setters for predefined properties at the click of a button

How to acess a public variable inside a private variable in PHP

I have the following class in PHP:
class myClass extends AJAX_Callable {
public static $classGlobal = 'myVariable' ;
private static $types_fields = array(
'myArray' => array(
'name' => $classGlobal,
'age' => 'twenty five')
);
}
But this is not working
From the documentation:
Class member variables are called "properties". You may also see them referred to using other terms such as "attributes" or "fields", but for the purposes of this reference we will use "properties". They are defined by using one of the keywords public, protected, or private, followed by a normal variable declaration. This declaration may include an initialization, but this initialization must be a constant value--that is, it must be able to be evaluated at compile time and must not depend on run-time information in order to be evaluated.
the only way I can think of doing it would be to move things into a constructor. But its still a really weird thing to want to do. There wouldnt be any point in using static as you cant use it in the conventional way.
class myClass extends AJAX_Callable {
public static $classGlobal;
private static $types_fields;
public function __construct() {
// parent::__construct(); // if you have one
$this->classGlobal = 'myVariable';
$this->types_fields = array(
'myArray' => array(
'name' => $this->classGlobal,
'age' => 'twenty five')
);
}
public function getTypes() {
return $this->types_fields;
}
}
$foo = new myClass();
var_dump($foo->getTypes());
fiddle

How to inherit parent class array properties by merging array?

I often use properties in my classes that store an array of options. I'd like to be able to somehow merge those options from defaults declared in a parent class.
I demonstrated with some code.
class A
{
public $options = array('display'=>false,'name'=>'John');
}
class B extends A
{
public $options = array('name'=>'Mathew');
}
Now when I create B, then I'd like $options to contain a merged array from A::options
What happens now is this.
$b = new B();
print_r($b);
array('name'=>'Mathew');
I would like something like this using array_merge_recursive().
array('display'=>false,'name'=>'Mathew');
Maybe it's something I could do in the constructor?
Is it possible to make this a behavior of class A? So that I don't always have to implement the same code in all subclasses.
Could I use reflection to auto find array properties in both classes and merge them?
In addition to the previous answers, another approach that may be suited for certain cases would be to use PHP Reflection or built-in class functions. Here is a basic example using the latter:
class Organism
{
public $settings;
public $defaults = [
'living' => true,
'neocortex' => false,
];
public function __construct($options = [])
{
$class = get_called_class();
while ($class = get_parent_class($class)) {
$this->defaults += get_class_vars($class)['defaults'];
}
$this->settings = $options + $this->defaults;
}
}
class Animal extends Organism
{
public $defaults = [
'motile' => true,
];
}
class Mammal extends Animal
{
public $defaults = [
'neocortex' => true,
];
}
$fish = new Animal();
print_r($fish->settings); // motile: true, living: true, neocortex: false
$human = new Mammal(['speech' => true]);
print_r($human->settings); // motile: true, living: true, neocortex: true, speech: true
I realize I changed your interface from a public variable to a method, but maybe it works for you. Beware, adding a naive setOps($ops) method may work unexpected if you allow the parent ops to continue to be merged in.
class A
{
private $ops = array('display'=>false, 'name'=>'John');
public function getops() { return $this->ops; }
}
class B extends A
{
private $ops = array('name'=>'Mathew');
public function getops() { return array_merge(parent::getOps(), $this->ops); }
}
class c extends B
{
private $ops = array('c'=>'c');
public function getops() { return array_merge(parent::getOps(), $this->ops); }
}
$c = new C();
print_r($c->getops());
out:
Array
(
[display] =>
[name] => Mathew
[c] => c
)
You can use a simple pattern like so:
abstract class Parent {
protected $_settings = array();
protected $_defaultSettings = array(
'foo' => 'bar'
);
public __construct($settings = array()) {
$this->_settings = $settings + $this->_defaultSettings;
}
}
In this way it's easily possible to modify the defaults applied in child classes:
class Child extends Parent {
protected $_defaultSettings = array(
'something' => 'different';
);
}
Or to apply something more complex:
class OtherChild extends Parent {
function __construct($settings = array()) {
$this->_defaultSettings = Configure::read('OtherChild');
return parent::__construct($settings);
}
}
Merging variables
Cake does come with a function for merging variables. It's used for controller properties such as components, helpers etc. But be careful applying this function to none trivial arrays - you may find that it doesn't quite do what you want/expect.

Can I set a PHP class property from an existing variable?

I am trying to figure out how I want to handle settings in my PHP app. I have pretty much decide that I would like to use a Confg class file so it will be autoloaded and flexible in the future. Below is some stuff I was playing with.
I know you cannot set a variable to popluate a Constant so I then try to use a public static property.
Why can I not set public static $ip = $_SERVER['REMOTE_ADDR']; ??
<?php
// config.class.php
class Config
{
const URL = 'http://www.foo.com';
const DB_User = 'dbname';
public static $test = 'test string';
public static $ip = $_SERVER['REMOTE_ADDR'];
}
///////////////////////////////////////////////////////
//index.php
// works
echo Config::URL;
// works
echo Config::$test;
// DOES NOT WORK
echo Config::$ip;
?>
members must be initialized with a constant expression (like a string constant, numeric literal, etc). php will give a parse error if you try to initialize a member with a dynamic expression (like the value of a variable or a function call)...
this is unlike some other langs like python, or javascript which consider class definitions to be on par with executable expressions... so your idea here is very functional, but php doesn't support it now at least...
but there are some alternatives to deal with this:
add the initialization after the class definition:
class C {...}
C::$var = $_SERVER['REMOTE_ADDR'];
or have an init function:
function init()
{
if (self::$init === false) {
self::$var = $_SERVER['REMOTE_ADDR'];
self::$init = true;
}
}
C::init();
or with newer php you can use the __autoload() hook to do some static initializations...
Although this does not answer your question (jspcal answers it correctly), a quick solution that might fit your needs would to use the Singleton design pattern. Here is an alternative:
<?php
// config.class.php
class Config
{
/**
* Instance container
* #var Config
*/
private static $instance = null;
/**
* Constants container
* #var array
*/
private $constants = array(
'url' => 'http://www.foo.com/',
'db_user' => 'dbname'
);
/**
* Options container
* #var array
*/
private $options = array();
/**
* Don't allow outside initialization
*/
private function __construct()
{
// Set options (could load from external source)
$this->options = array(
'test' => 'test string',
'ip' => $_SERVER['REMOTE_ADDR']
);
}
/**
* Disable cloning
*/
private function __clone() { }
/**
* Get new instance of class
*/
public function getInstance()
{
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Retrieve value with constants being a higher priority
* #param $key Key to get
*/
public function __get( $key )
{
if ( isset( $this->constants[$key] ) ) {
return $this->constants[$key];
} elseif ( isset( $this->options[$key] ) ) {
return $this->options[$key];
}
}
/**
* Set a new or update a key / value pair
* #param $key Key to set
* #param $value Value to set
*/
public function __set( $key, $value )
{
$this->options[$key] = $value;
}
}
///////////////////////////////////////////////////////
//index.php
// works
$config = Config::getInstance();
echo $config->url;
echo $config->test;
echo $config->ip;
Updated: Not sure if you want the constants / options with that kind of priority. It's just an example.
try to use define() to do that (give a constant!):
// config.class.php
define(REMOTE_ADDR, $_SERVER['REMOTE_ADDR']);
class Config
{
const URL = 'http://www.foo.com';
const DB_User = 'dbname';
public static $test = 'test string';
public static $ip = REMOTE_ADDR;
}
Not a direct answer to your question, but why don't you use a less hardcoded approach, e.g. a generic Config class you can reuse in your apps
// MyConfig.php
class MyConfig {
protected $_data;
public function __construct($path)
{
$config = include $path;
$this->_data = $config;
}
public function __get($val)
{
if(array_key_exists($val, $this->_data)) {
return $this->_data['val'];
} else {
trigger_error("Key $val does not exist", E_USER_NOTICE);
}
}
}
that you can fill from an array for a specific app
// app-config.php
return array(
'ip' => $_SERVER['REMOTE_ADDR'],
'url' => 'http://www.foo.com';
'db' => array(
'host' => 'foo.com',
'port' => 3306
),
'caching' => array(
'enabled' => false
)
);
and then use in your bootstrap like
$config = new MyConfig('/path/to/app-config.php');
This is not answering your question, but in my opinion, a better way to deal with configurations is actually to use a real configuration file, like an INI or XML file.
You could use e.g. the Zend Config class to read and write such files (and this class can even deal with a plain PHP array as configuration.
In the end this will make your code easier to maintain.
After reading other answers and comments you might also be interested in the Zend Registry class.
In general I would advice to use a framework or readymade components for such stuff. You don't need to reinvent the wheel and you can profit from the other's experience.
It won't work because PHP just doesn't allow it.
Generally I wouldn't suggest putting your app's config into a class (at least not in PHP), there are no real advantages in my opinion - better put it into a global array or so :)
PHP simply doesn't allow that. If you think about it, it doesn't make sense to have the REMOTE_ADDR as part of your configuration data anyway. You can consider it to be part of the request data, and not the application configuration.
As for your configuration, I would suggest using an .ini file for your configuration because PHP has a built-in INI parser (see the parse_ini_file function). This would be consistent with PHP's choice of INI file format for its own configuration (i.e., php.ini).
Check out this question, for example: What's Is the Best File Format for Configuration Files
The simple reason why you cannot assign $_SERVER['REMOTE_ADDR'] to the class attribute is this.
In PHP, Class attribute value can only be a data type and not a variable, and since $_SERVER is a global array variable, it can't be assigned to an attribute.

Categories