Constructing a CComponent - php

BranchSalaries is a class derived from CComponent. It has get/set methods for month and year properties.
The following code does not work (more specifically, does not initialize year and month properties):
new BranchSalaries(array('id'=>'branch_salaries', 'year'=>$year, 'month'=>$month));
What is the simplest way to make a class which can be initialized passing an array to the constructor?
Correction BranchSalaries is derived not directly from CComponent but from CDataProvider.

function __construct($arr){
foreach($arr AS $key=>$value){
$this->$key = $value;
}
}
You need a constructor that can process an array.
Edit: If you need to use the setters: (this assumes the setter is like setId() not setid()
function __construct($arr){
foreach($arr AS $key=>$value){
$method = "set".ucfirst($key);
$this->$method($value);
}
}

To setup class as Yii does, you can use Yii::createComponent(), and then there is no need to create cunstomized constructor. I your case this would be:
Yii::createComponent(array(
'class' => 'BranchSalaries',
'id'=>'branch_salaries',
'year'=>$year,
'month'=>$month
));
While it does not look user friendly, it can be used for automatic object creation.
class parameter can also be used with path alias:
Yii::createComponent(array(
'class' => 'ext.someExtension.BranchSalaries',
'id'=>'branch_salaries',
'year'=>$year,
'month'=>$month
));
Also it set class properties from outside class scope, so getters and setters will be automatically used.

Related

Can the __set and __get methods behave differently inside and outside of a class?

I'm playing around with OOP in PHP and am trying to write a basic Session class which will create a Session array. When instantiating the class, the user will provide the first dimension of the array, and then I'd like them to be able to add any variable/value combinations to that Session. As an example:
$session = new My_Session('testing');
$session ->boy = 'girl';
should be equivalent to typing $_SESSION['testing']['boy'] = 'girl';
I thought that I could use PHP's magic __set and __get methods, but my output is not what is expected. Below I have my class, an example, and my results:
The Class:
class My_Session {
function __construct($session_namespace) {
$this->session_namespace = $session_namespace;
}
public function __set($name, $value) {
$_SESSION[$this->session_namespace][$name] = $value;
}
public function __get($name){
return $name;
}
}
The test:
$session = new My_Session('testing');
$session ->boy = 'girl';
$session ->cow = 'blah';
print_r($_SESSION);
$session2 = new My_Session('testing2');
$session2 ->boy = 'girl2';
$session2 ->cow = 'blah2';
print_r($_SESSION);
The funky results:
Array
(
[session_namespace] => Array
(
[session_namespace] => testing
[boy] => girl
[cow] => blah
)
)
Array
(
[session_namespace] => Array
(
[session_namespace] => testing2
[boy] => girl2
[cow] => blah2
)
)
What I imagine is happening is that my __set and __get methods are being defined the same way outside of the class, as well as inside the class; is there anyway to avoid this so that I can keep the $session->boy = 'girl' syntax for the user instantiating the class, but at the same time, have $this->session_namespace = $session_namespace behave correctly inside the class (i.e. if I didn't have custom __get and __set functions)?
Thanks so much!
The magic __get and __set methods are only called when accessing a property which has not been explicitly declared. The reason your code is working as shown is that $session_namespace is just another undeclared property, so setting it calls __set, regardless of where that call is made.
What you need to do, therefore, is add a line declaring that property to your class definition such as private $session_namespace (it doesn't matter if it's public, protected, or private, but the general rule is to restrict things first, and relax the restrictions only when needed).
PHP will see the declared property, set it as a normal variable, and not call __set, while any other name will still call the magic methods. As a bonus, a private or protected variable will be considered to not exist when called from a public context, so it should even be possible to set $_SESSION['testing']['session_namespace'] if you for some reason wanted to.

php: Instantiate class on-the-fly

Is it possible to instantiate a class from a string, without declaring another variable before ?
It's usually done writing
$className = 'myClass'
$instance = new $className();
but it could be handy to have it shorter like for example
$instance = new ${'className'}();
The purpose is to have objects created (under condition) inside a loop without use of extra vars...
Edit : $className is dynamic, it is hard coded above to explain the situation
See factory pattern.
class Foo {
static function factory($class, array $args = null) {
return new $class($args);
}
}
// class factoring; returns a new instance of requested class ($className)
Foo::factory($className);
I added optional arguments array if you want to set some class properties.
// pass some values for class constructor
Foo::factory($className, array('arg1' => 1, 'arg2' => 2, 'args3' => 3));
Furthermore, you can build "fluid" interfaces so you can "chain" methods when you use that pattern:
Foo::factory($className)->method1()->method2(array('param' => 'value'))->etc();
where method1(), method2() must return $this (the object itself) to chain multiple method calls in one line.
You could make a factory function (or class/method) that takes a class name as a parameter, and then call it with the result of your dynamic PHP code that generates the string. You might consider it a bit cleaner but it's not going to save you any memory or speed.
class foo { }
function factory($class) { return new $class(); }
foreach (...) {
$instance = factory(<some code that returns the string 'foo'>);
}
It's one extra variable, does it really make much of a difference? The answer is that unless you use eval (which comes with security issues) it isn't possible to do it any shorter than your first example.

Access class constant and static method from string

I have a string containing the class name and I wish to get a constant and call a (static) method from that class.
<?php
$myclass = 'b'; // My class I wish to use
$x = new x($myclass); // Create an instance of x
$response = $x->runMethod(); // Call "runMethod" which calls my desired method
// This is my class I use to access the other classes
class x {
private $myclass = NULL;
public function __construct ( $myclass ) {
if(is_string($myclass)) {
// Assuming the input has a valid class name
$this->myclass = $myclass;
}
}
public function runMethod() {
// Get the selected constant here
print $this->myclass::CONSTANT;
// Call the selected method here
return $this->myclass::method('input string');
}
}
// These are my class(es) I want to access
abstract class a {
const CONSTANT = 'this is my constant';
public static function method ( $str ) {
return $str;
}
}
class b extends a {
const CONSTANT = 'this is my new constant';
public static function method ( $str ) {
return 'this is my method, and this is my string: '. $str;
}
}
?>
As I expected (more or less), using $variable::CONSTANT or $variable::method(); doesn't work.
Before asking what I have tried; I've tried so many things I basically forgot.
What's the best approach to do this? Thanks in advance.
To access the constant, use constant():
constant( $this->myClass.'::CONSTANT' );
Be advised: If you are working with namespaces, you need to specifically add your namespace to the string even if you call constant() from the same namespace!
For the call, you'll have to use call_user_func():
call_user_func( array( $this->myclass, 'method' ) );
However: this is all not very efficient, so you might want to take another look at your object hierarchy design. There might be a better way to achieve the desired result, using inheritance etc.
in php 7 you can use this code
echo 'my class name'::$b;
or
#Uncomment this lines if you're the input($className and $constName) is safe.
$reg = '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/';
if(preg_match($reg,$className) !== 1 || preg_match($reg,$constName) !== 1)
throw new \Exception('Oh, is it an attack?');
$value = eval("return $className::$constName;");
You can achieve it by setting a temporary variable. Not the most elegant way but it works.
public function runMethod() {
// Temporary variable
$myclass = $this->myclass;
// Get the selected constant here
print $myclass::CONSTANT;
// Call the selected method here
return $myclass::method('input string');
}
I guess it's to do with the ambiguity of the ::, at least that what the error message is hinting at (PHP Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM)
Use call_user_func to call static method:
call_user_func(array($className, $methodName), $parameter);
Classes defined as abstract may not be instantiated, and any class that contains at least one abstract method must also be abstract. Methods defined as abstract simply declare the method's signature - they cannot define the implementation.
When inheriting from an abstract class, all methods marked abstract in the parent's class declaration must be defined by the child; additionally, these methods must be defined with the same (or a less restricted) visibility. For example, if the abstract method is defined as protected, the function implementation must be defined as either protected or public, but not private. Furthermore the signatures of the methods must match, i.e. the type hints and the number of required arguments must be the same. This also applies to constructors as of PHP 5.4. Before 5.4 constructor signatures could differ.
Refer to http://php.net/manual/en/language.oop5.abstract.php
This might just be tangential to the subject but, while searching for my own issue I found that the accepted answer pointed me in the right direction, so I wanted to share my problem & solution in case someone else might be stuck in a similar fashion.
I was using the PDO class and was building some error options from an ini config file. I needed them in an associative array in the form: PDO::OPTION_KEY => PDO::OPTION_VALUE, but it was of course failing because I was trying to build the array with just PDO::$key => PDO::$value.
The solution (inspired from the accepted answer):
$config['options'] += [constant('PDO::'.$key) => constant('PDO::'.$option)];
where everything works if you concatenate the class name and the Scope Resolution Operator as a string with the variable and get the constant value of the resulting string through the constant function (more here).
Thank you and I hope this helps someone else!

PHP __get and __set magic methods

Unless I'm completely mistaken, the __get and __set methods are supposed to allow overloading of the → get and set.
For example, the following statements should invoke the __get method:
echo $foo->bar;
$var = $foo->bar;
And the following should use the __set method:
$foo->bar = 'test';
This was not working in my code, and is reproducible with this simple example:
class foo {
public $bar;
public function __get($name) {
echo "Get:$name";
return $this->$name;
}
public function __set($name, $value) {
echo "Set:$name to $value";
$this->$name = $value;
}
}
$foo = new foo();
echo $foo->bar;
$foo->bar = 'test';
echo "[$foo->bar]";
This only results in:
[test]
Putting some die() calls in there shows that it is not hitting it at all.
For now, I just said screw it, and am manually using __get where it's needed for now, but that's not very dynamic and requires knowledge that the 'overloaded' code is in fact not being called unless specifically called. I'd like to know if this is either not supposed to function the way I've understood that it should or why this is not working.
This is running on php 5.3.3.
__get, __set, __call and __callStatic are invoked when the method or property is inaccessible. Your $bar is public and therefor not inaccessible.
See the section on Property Overloading in the manual:
__set() is run when writing data to inaccessible properties.
__get() is utilized for reading data from inaccessible properties.
The magic methods are not substitutes for getters and setters. They just allow you to handle method calls or property access that would otherwise result in an error. As such, there are much more related to error handling. Also note that they are considerably slower than using proper getter and setter or direct method calls.
I'd recommend to use an array for storing all values via __set().
class foo {
protected $values = array();
public function __get( $key )
{
return $this->values[ $key ];
}
public function __set( $key, $value )
{
$this->values[ $key ] = $value;
}
}
This way you make sure, that you can't access the variables in another way (note that $values is protected), to avoid collisions.
From the PHP manual:
__set() is run when writing data to inaccessible properties.
__get() is utilized for reading data from inaccessible properties.
This is only called on reading/writing inaccessible properties. Your property however is public, which means it is accessible. Changing the access modifier to protected solves the issue.
To expand on Berry's answer, that setting the access level to protected allows __get and __set to be used with explicitly declared properties (when accessed outside the class, at least) and the speed being considerably slower, I'll quote a comment from another question on this topic and make a case for using it anyway:
I agree that __get is more slow to a custom get function (doing the same things), this is 0.0124455 the time for __get() and this 0.0024445 is for custom get() after 10000 loops. – Melsi Nov 23 '12 at 22:32 Best practice: PHP Magic Methods __set and __get
According to Melsi's tests, considerably slower is about 5 times slower. That is definitely considerably slower, but also note that the tests show that you can still access a property with this method 10,000 times, counting time for loop iteration, in roughly 1/100 of a second. It is considerably slower in comparison with actual get and set methods defined, and that is an understatement, but in the grand scheme of things, even 5 times slower is never actually slow.
The computing time of the operation is still negligible and not worth considering in 99% of real world applications. The only time it should really be avoided is when you're actually going to be accessing the properties over 10,000 times in a single request. High traffic sites are doing something really wrong if they can't afford throwing a few more servers up to keep their applications running. A single line text ad on the footer of a high traffic site where the access rate becomes an issue could probably pay for a farm of 1,000 servers with that line of text. The end user is never going to be tapping their fingers wondering what is taking the page so long to load because your application's property access takes a millionth of a second.
I say this speaking as a developer coming from a background in .NET, but invisible get and set methods to the consumer is not .NET's invention. They simply aren't properties without them, and these magic methods are PHP's developer's saving grace for even calling their version of properties "properties" at all. Also, the Visual Studio extension for PHP does support intellisense with protected properties, with that trick in mind, I'd think. I would think with enough developers using the magic __get and __set methods in this way, the PHP developers would tune up the execution time to cater to the developer community.
Edit: In theory, protected properties seemed like it'd work in most situation. In practice, it turns out that there's a lot of times you're going to want to use your getters and setters when accessing properties within the class definition and extended classes. A better solution is a base class and interface for when extending other classes, so you can just copy the few lines of code from the base class into the implementing class. I'm doing a bit more with my project's base class, so I don't have an interface to provide right now, but here is the untested stripped down class definition with magic property getting and setting using reflection to remove and move the properties to a protected array:
/** Base class with magic property __get() and __set() support for defined properties. */
class Component {
/** Gets the properties of the class stored after removing the original
* definitions to trigger magic __get() and __set() methods when accessed. */
protected $properties = array();
/** Provides property get support. Add a case for the property name to
* expand (no break;) or replace (break;) the default get method. When
* overriding, call parent::__get($name) first and return if not null,
* then be sure to check that the property is in the overriding class
* before doing anything, and to implement the default get routine. */
public function __get($name) {
$caller = array_shift(debug_backtrace());
$max_access = ReflectionProperty::IS_PUBLIC;
if (is_subclass_of($caller['class'], get_class($this)))
$max_access = ReflectionProperty::IS_PROTECTED;
if ($caller['class'] == get_class($this))
$max_access = ReflectionProperty::IS_PRIVATE;
if (!empty($this->properties[$name])
&& $this->properties[$name]->class == get_class()
&& $this->properties[$name]->access <= $max_access)
switch ($name) {
default:
return $this->properties[$name]->value;
}
}
/** Provides property set support. Add a case for the property name to
* expand (no break;) or replace (break;) the default set method. When
* overriding, call parent::__set($name, $value) first, then be sure to
* check that the property is in the overriding class before doing anything,
* and to implement the default set routine. */
public function __set($name, $value) {
$caller = array_shift(debug_backtrace());
$max_access = ReflectionProperty::IS_PUBLIC;
if (is_subclass_of($caller['class'], get_class($this)))
$max_access = ReflectionProperty::IS_PROTECTED;
if ($caller['class'] == get_class($this))
$max_access = ReflectionProperty::IS_PRIVATE;
if (!empty($this->properties[$name])
&& $this->properties[$name]->class == get_class()
&& $this->properties[$name]->access <= $max_access)
switch ($name) {
default:
$this->properties[$name]->value = $value;
}
}
/** Constructor for the Component. Call first when overriding. */
function __construct() {
// Removing and moving properties to $properties property for magic
// __get() and __set() support.
$reflected_class = new ReflectionClass($this);
$properties = array();
foreach ($reflected_class->getProperties() as $property) {
if ($property->isStatic()) { continue; }
$properties[$property->name] = (object)array(
'name' => $property->name, 'value' => $property->value
, 'access' => $property->getModifier(), 'class' => get_class($this));
unset($this->{$property->name}); }
$this->properties = $properties;
}
}
My apologies if there are any bugs in the code.
It's because $bar is a public property.
$foo->bar = 'test';
There is no need to call the magic method when running the above.
Deleting public $bar; from your class should correct this.
Best use magic set/get methods with predefined custom set/get Methods as in example below. This way you can combine best of two worlds. In terms of speed I agree that they are a bit slower but can you even feel the difference. Example below also validate the data array against predefined setters.
"The magic methods are not substitutes for getters and setters. They
just allow you to handle method calls or property access that would
otherwise result in an error."
This is why we should use both.
CLASS ITEM EXAMPLE
/*
* Item class
*/
class Item{
private $data = array();
function __construct($options=""){ //set default to none
$this->setNewDataClass($options); //calling function
}
private function setNewDataClass($options){
foreach ($options as $key => $value) {
$method = 'set'.ucfirst($key); //capitalize first letter of the key to preserve camel case convention naming
if(is_callable(array($this, $method))){ //use seters setMethod() to set value for this data[key];
$this->$method($value); //execute the setters function
}else{
$this->data[$key] = $value; //create new set data[key] = value without seeters;
}
}
}
private function setNameOfTheItem($value){ // no filter
$this->data['name'] = strtoupper($value); //assign the value
return $this->data['name']; // return the value - optional
}
private function setWeight($value){ //use some kind of filter
if($value >= "100"){
$value = "this item is too heavy - sorry - exceeded weight of maximum 99 kg [setters filter]";
}
$this->data['weight'] = strtoupper($value); //asign the value
return $this->data['weight']; // return the value - optional
}
function __set($key, $value){
$method = 'set'.ucfirst($key); //capitalize first letter of the key to preserv camell case convention naming
if(is_callable(array($this, $method))){ //use seters setMethod() to set value for this data[key];
$this->$method($value); //execute the seeter function
}else{
$this->data[$key] = $value; //create new set data[key] = value without seeters;
}
}
function __get($key){
return $this->data[$key];
}
function dump(){
var_dump($this);
}
}
INDEX.PHP
$data = array(
'nameOfTheItem' => 'tv',
'weight' => '1000',
'size' => '10x20x30'
);
$item = new Item($data);
$item->dump();
$item->somethingThatDoNotExists = 0; // this key (key, value) will trigger magic function __set() without any control or check of the input,
$item->weight = 99; // this key will trigger predefined setter function of a class - setWeight($value) - value is valid,
$item->dump();
$item->weight = 111; // this key will trigger predefined setter function of a class - setWeight($value) - value invalid - will generate warning.
$item->dump(); // display object info
OUTPUT
object(Item)[1]
private 'data' =>
array (size=3)
'name' => string 'TV' (length=2)
'weight' => string 'THIS ITEM IS TOO HEAVY - SORRY - EXIDED WEIGHT OF MAXIMUM 99 KG [SETTERS FILTER]' (length=80)
'size' => string '10x20x30' (length=8)
object(Item)[1]
private 'data' =>
array (size=4)
'name' => string 'TV' (length=2)
'weight' => string '99' (length=2)
'size' => string '10x20x30' (length=8)
'somethingThatDoNotExists' => int 0
object(Item)[1]
private 'data' =>
array (size=4)
'name' => string 'TV' (length=2)
'weight' => string 'THIS ITEM IS TOO HEAVY - SORRY - EXIDED WEIGHT OF MAXIMUM 99 KG [SETTERS FILTER]' (length=80)
'size' => string '10x20x30' (length=8)
'somethingThatDoNotExists' => int 0
Drop the public $bar; declaration and it should work as expected.
Intenta con:
__GET($k){
return $this->$k;
}
_SET($k,$v){
return $this->$k = $v;
}

php objects define and set attributes in constructor

Is it possible to define attributes to a class in the constructor.
For instance I pass a Associative Array to a class constructor and I want the attributes to that class to be declared and set based on what is in the Associative Array.
TIA
class Foo{
function __construct($arr){
foreach($arr as $k => $v)
$this->$k = $v;
}
}
You can review the constructor/desctructor manual and properties manual.
to be noted since you don't define the properties in the class all are set to public which IMHO is kind of dangerous. I think it might be possible to achieve the same thing using the reflection. I just checked more in depth the reflection and it is not possible (with PHP5), since it would make sense to be able to do that from the reflection it might come with PHP6.
full sample
<?php
class Foo{
function __construct($arr){
foreach($arr as $k => $v)
$this->$k = $v;
}
function getBar(){
return $this->bar;
}
}
$bar = new Foo(array(
'bar' => 'bar',
'foo' => 'foo'
)
);
var_dump($bar->bar);
?>
Yes, you can do that. off top of my head
public function __constructor($data){
foreach($data as $k=>$v){
$this->{$k} = $v;
}
}
This should do it, and of course you need to math define the fields.
I m not too sure if it s {$k} or ${k} or ${$k} but either of these should work.
You can certainly do this, as RageZ describes above, but I don't think I would recommend doing it. What this does is creates too loose of a "contract" between the users of this class - i.e. nobody really knows which properties the class has.
Instead of defining the properties on the fly, I would bet that you have a pre-defined set of "object property sets" that could in turn define a class hierarchy.
So instead of doing
if I get an array with "a = 2 and b =
3", create object with $obj->a = 3
and $obj-b = 3
if I get an array with
"c = 5", create an object with
$obj->c = 5
Do this:
define a class for MyObjectOfTypeOne, that has properties $a and $b
define a class for MyObjectOfTypeTwo that has property $c
Then, use the Factory pattern to generate the right type of object depending on the parameters passed to the static factory method.

Categories