I want to do a massive assignement of my protected vars, i used this code:
protected $_productName = '';
protected $_price = 0;
protected $_categoyId = 0;
public function setAttributes($attributes)
{
foreach($attributes as $key => $val)
{
$var = '_' . $key;
$this->$var = $val;
}
}
$attributes = array('productName'=>'some Product', 'price' => 10, 'categoryId' => 5) for exapmle.
the code above works for me but I feel that it's not clean. Is there any better solution to do that?
thanx.
Code is clean, nothing bad about it. You could maybe additionally see if class field exists before setting it - so you could assure you are not setting any additional fields, which are not defined in a class.
Also to make the code a little shorter, you could do:
$this->{"_{$key}"} = $val;
This is matter of taste what you like - your variant is fine as well.
What you're doing is fine. I would add a check for the property:
foreach($attributes as $key => $val)
{
$var = '_' . $key;
if (property_exists($this, $var))
{
$this->$var = $val;
}
}
Your code is pretty much as a clean as it gets for mass-assignment. There are alternatives, like using array_walk() instead of a foreach loop, but I find loops to be cleaner and easier to understand quickly in situations like these.
it looks ok to me, but if $attributes is always an array probably you should add this to avoid errors
public function setAttributes($attributes=array())
doing that you won't receive a error if attributes is empty because initialize the $attributes array
You can use Magic Methods. Assign array as property value. And for every time you call variable like $this->var invoke __get method
public function setAttributes($attributes)
{
$this->attributes = $attributes;
}
function __get($var) {
if(isset($this->attributes[$var])) return $this->attributes[$var];
return false;
}
function __set($key, $var) {
}
I also used something like the following code awhile ago as a test:
private $data = array();
public function __get($name)
{
if (array_key_exists($name, $this->data))
{
return $this->data[$name];
}
}
public function __set($name, $value)
{
$this->data[trim($name)] = trim($value);
}
Related
I wrote the __set in order to make sure something is done before setter anything
public function __setter($name, $value) {
if ($this->needDoSomething) {$this->doSomeThingNecessaryBeforeSetAnything();}
$this->needDoSomething = false;
$this->$name = $value;
}
However, the magic method will impact performance. In the same class, I have another function
private function loadData() {
if ($this->needDoSomething) {$this->doSomeThingNecessaryBeforeSetAnything();}
foreach ($data as $key=>$value) {
$this->$key = $value;
}
}
Since the doSomeThingNecessaryBeforeSetAnything() is already called, I don't need to call __set, but would like to set the property directly. This will largely help on performance.
However, I cannot remove the __set, because there are lot's of legacy code outside the class, and I need it to make the logic correct.
Seems with PHP I cannot add or remove methods to object on the fly. Any ideas?
Edit: The performance is caused by __set itself, because I have large number of objects and each have large number of properties to set. The code below shows __set is 6 times slower than set properties directly.
class Test {}
class Test2 {
public function __set($name, $value) {
$this->$name = $value;
}
}
function runOnObject($o) {
$t = microtime(true);
for ($i=0; $i<100000; $i++) {
$prop = "prop{$i}";
$o->$prop = $i;
}
echo "".(microtime(true) - $t)." second ";
}
echo runOnObject(new Test()). "(With out __set)<p>";
echo runOnObject(new Test2()). "(With __set)";
The result:
0.084139823913574 second (With out __set)
0.47258400917053 second (With __set)
If you add a __get, you can store the properties in a private data structure (eg. an array), allowing direct access to the data from within the class, while still maintaining the same public interface.
Like this:
class Container
{
private $properties = array();
public function __set($key, $value)
{
$this->doSomethingExpensive();
$this->properties[$key] = $value;
}
public function __get($key)
{
if (!isset($this->properties[$key])) {
throw new Exception('Invalid Property ' . $key);
}
return $this->properties[$key];
}
public function loadData($data)
{
$this->doSomethingExpensive();
foreach ($data as $key => $value) {
$this->properties[$key] = $value;
}
}
private function doSomethingExpensive()
{
echo 'Doing Work...' . PHP_EOL;
}
}
// Example
$c = new Container();
$c->loadData(array(
'alpha' => 'A',
'beta' => 'D',
'charlie' => 'C',
'delta' => 'D'
));
var_dump($c->alpha, $c->beta);
If this will be any faster, I don't know, it depends on your specific use case as you avoid running the "expensive" code repeatedly, but there will be some overhead from using __get.
Obviously not exactly what you want, but another thought... Use ArrayAccess for the dynamic set functionality instead of __set. Though you'll have to update the 'tons' of client code (not sure how unrealistic that may be).
<?php
class A implements ArrayAccess
{
private $_doThings = false;
public function __construct() {
$this->loadData();
}
public function offsetExists($k) { return isset($this->$k); }
public function offsetGet($k) { return $this->$k; }
public function offsetUnset($k) { unset($this->$k); }
public function offsetSet($k, $v) {
if($this->_doThings)
$this->doSomeThingNecessaryBeforeSetAnything();
$this->$k = $v;
}
private function doSomeThingNecessaryBeforeSetAnything() {
echo __METHOD__ . PHP_EOL;
}
private function loadData() {
$this->doSomeThingNecessaryBeforeSetAnything();
$this->_doThings = false;
foreach (array('a' => 1, 'b' => 2, 'c' => 3) as $key=>$value) {
$this->$key = $value;
}
}
}
Demo code
$a = new A();
// Need to change all calls like $a->c = 4 to $a['c'] = 4
$a['c'] = 4;
var_dump($a);
So there's a painful code change required, but you get the best of both worlds. The dynamic behavior, and the performance.
class Car {
function __construct() {
// echo 'car con';
}
function setInfo($car_arr) {
foreach ($car_arr as $key => $value) {
$this->{$key} = $value;
}
}
}
i try to access like bellow
$car1 = new Car();
$car1->setInfo('make', 'Toyota')->setInfo('model', 'scp10');
that gave to me bellow error
Call to a member function setInfo() on a non-object
how can i change setInfo() method call $car1->setInfo('make', 'Toyota')->setInfo('model', 'scp10'); after that car class set $make = 'Toyota', like that
how can i print this object like
bellow
make = Toyota
model = scp10
You need to add return $this; in the end of your method for chain-like calls.
change the setInfo code to return itself like:
function setInfo($car_arr,$car_val=null) {
if(is_array($car_arr)){
foreach ($car_arr as $key => $value) {
$this->{$key} = $value;
}
}else if(is_string($car_arr) && $car_val != null){
$this->{$car_arr} = $car_val;
}
return $this;
}
now you can chain the functions because its returning itself.
also if you want to call it like you want ( like $this->setInfo("make","Ford")) you need to add an else on is_array and add an optional parameter like shown in the code above
To combine all answers into one (well, except #EaterOfCorpses):
<?php
class Car {
private $data = array();
function setInfo(array $carInfo) {
foreach ($carInfo as $k => $v) {
$this->data[$k] = $v;
}
return $this;
}
function __set($key, $val) {
$this->data[$key] = $val;
}
function __get($key) {
return $this->data[$key];
}
}
$car = new Car();
$car->setInfo(['make' => 'Toyota', 'warranty' => '5 years']);
Note that there's no reason to return $this if you are setting all the properties at once.
Edited to add: also include magic getter/setter idea from Mark Baker just for the fun of it.
You should use $car1->setInfo('make', 'Toyota') only once. That's because you create a car, then set info, and then you want to set info to info, but you can't set info to info.
It's called a fluent interface
Add
return $this;
as the last line of your setInfo() method
Use array syntax: $car1->setInfo(array('make', 'Toyota'))
You can return $this in your function (if you have php 5.4):
function setInfo($car_arr) {
...
return $this;
}
So I am building a helper class that would store all get variables from a url, remove trailing spaces and return it so that other methods can use them.
The problem is that only the first value gets stored.
The url looks like:
https://pay.paymentgateway.com/index.php?name=xyz&amount=10.30&checksum=abcd
My code outputs:
Array
(
[name] => xyz
)
My code:
class helperboy
{
protected $cleanvariables = array();
public function store_get_variables($_GET)
{
foreach ($_GET as $key => $value)
{
return $this->cleanvalues[$key] = trim($value);
}
}
protected function display_variables()
{
echo "<pre>";
print_r($this->cleanvalues);
}
}
I know I am doing something silly and I would appreciate any help.
Also, how can I access specific variables like this in my other methods.:
$this->cleanvalues['name'];
$this->cleanvalues['amount'];
$this->cleanvalues['checksum'];
your return statement is the problem....
class helperboy
{
protected $cleanvariables = array();
public function store_get_variables($_GET)
{
foreach ($_GET as $key => $value)
{
$this->cleanvalues[$key] = trim($value);
}
return $this->cleanvalues;
}
protected function display_variables()
{
echo "<pre>";
print_r($this->cleanvalues);
}
}
Well, the problem is that...
public function store_get_variables($_GET)
{
foreach ($_GET as $key => $value)
{
return $this->cleanvalues[$key] = trim($value);
}
}
... the loop here will be executed just once. As soon as function hits return statement, it will abort this loop - and return immediately.
Yet I think there are some bigger problems here. First, I don't buy the idea of some omnipotent helper class that knows everything about everyone. If you intend to work with some cleaner request params, why don't just 'objectize' this instead:
class My_Http_Request
{
private $request;
protected function fillGetParams() {
$this->request['get'] = array_map('trim', $_GET);
}
public function getTrimmedParam($name) {
return $this->request['get'][$name];
}
public function __construct() {
$this->fillGetParams();
}
}
That's just an idea, not ready-made implementation (no checks for missing elements, no returning all params if 'getTrimmedParam' method is called without any arguments, etc.
I'm wondering if it's possible, and in case it is, how shoud I achive that:
$this->id <-- i have such thing. but to make it more usable i'd like to have $this->(and here to change the values)
for ex: I might have $this->id $this->allID $this->proj_id
how can I make so that actually I have $this->($myvariable here, that has a unique name in it)?
You can simply use this:
$variable = 'id';
if ( isset ( $this->{$variable} ) )
{
echo $this->{$variable};
}
Here is the solution : http://www.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.members
An example of using it is here :
class myClass {
/** Location for overloaded data. */
private $myProperties = array();
public function __set($name, $value)
{
$this->myProperties[$name] = $value;
}
public function __get($name)
{
if (array_key_exists($name, $this->myProperties))
{
return $this->data[$name];
}
}
}
You should check out the variable variables manual on the PHP site.
With that, it could look like:
<?php
echo ${'this->'.$yourvariable};
?>
I prefer to use call_user_func and pass the parameters as array instead.
public function dynamicGetterExample()
{
$property = 'name'; // as an example...
$getter = 'get'.ucfirst($property);
$value = call_user_func(array($this,$getter));
if (empty($value)) {
throw new \Exception('Required value is empty for property '.$property);
}
return $value;
}
I've been fooling with ArrayAccess and PHP's magic (__get, __set) for awhile now, and I'm stuck.
I'm trying to implement a class in which some properties, which are arrays, are read only. They will be set initially by the constructor, but should not be modifiable thereafter.
Using __get magic by reference, I can access array elements arbitrarily deep in the properties, and I was thinking I can throw exceptions when those properties are targeted via __set.
The problem is though, when I'm accessing the value of an array element, PHP is calling __get to return that part of the array by reference, and I have no knowledge of whether or not its a read or write action.
(The worst part is I knew this going in, but have been fooling with ArrayAccess as a possible workaround solution, given the properties were instances of an implemented object)
Simple example:
class Test{
public function &__get($key){
echo "[READ:{$key}]\n";
}
public function __set($key, $value){
echo "[WRITE:{$key}={$value}]\n";
}
}
$test = new Test;
$test->foo;
$test->foo = 'bar';
$test->foo['bar'];
$test->foo['bar'] = 'zip';
And the output:
[READ:foo]
[WRITE:foo=bar]
[READ:foo]
[READ:foo] // here's the problem
Realistically, I only need the value foo (as per my example) anyways, but I need to know it's a write action, not read.
I've already half accepted that this cannot be achieved, but I'm still hopeful. Does anyone have any idea how what I'm looking to accomplish can be done?
I was considering some possible workarounds with ArrayAccess, but so far as I can tell, I'll end up back at this spot, given I'm going to use the property notation that invokes __get.
Update: Another fun day with ArrayAccess.
(This is a different issue, but I suppose it works in. Posting just for kicks.)
class Mf_Params implements ArrayAccess{
private $_key = null;
private $_parent = null;
private $_data = array();
private $_temp = array();
public function __construct(Array $data = array(), $key = null, self $parent = null){
$this->_parent = $parent;
$this->_key = $key;
foreach($data as $key => $value){
$this->_data[$key] = is_array($value)
? new self($value, $key, $this)
: $value;
}
}
public function toArray(){
$array = array();
foreach($this->_data as $key => $value){
$array[$key] = $value instanceof self
? $value->toArray()
: $value;
}
return $array;
}
public function offsetGet($offset){
if(isset($this->_data[$offset])){
return $this->_data[$offset];
}
// if offset not exist return temp instance
return $this->_temp[$offset] = new self(array(), $offset, $this);
}
public function offsetSet($offset, $value){
$child = $this;
// copy temp instances to data after array reference chain
while(!is_null($parent = $child->_parent) && $parent->_temp[$child->_key] === $child){
$parent->_data[$child->_key] = $parent->_temp[$child->_key];
$child = $parent;
}
// drop temp
foreach($child->_temp as &$temp){
unset($temp);
}
if(is_null($offset)){
$this->_data[] = is_array($value)
? new self($value, null, $this)
: $value;
}else{
$this->_data[$offset] = is_array($value)
? new self($value, $offset, $this)
: $value;
}
}
public function offsetExists($offset){
return isset($this->_data[$offset]);
}
public function offsetUnset($offset){
unset($this->_data[$offset]);
}
}
You need to use a second class, implementing ArrayAccess, to use instead of your arrays. Then you will be able to control what is added to the array with the offsetSet() method:
class ReadOnlyArray implements ArrayAccess {
private $container = array();
public function __construct(array $array) {
$this->container = $array;
}
public function offsetSet($offset, $value) {
throw new Exception('Read-only');
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
if (! array_key_exists($offset, $this->container)) {
throw new Exception('Undefined offset');
}
return $this->container[$offset];
}
}
You can then initialize your ReadOnlyArray with your original array:
$readOnlyArray = new ReadOnlyArray(array('foo', 'bar'));
You could not return by ref, which would solve the problem of changability, but would not allow changing of some values that are allowed to be changed.
Alternatively you need to wrap every returned array in ArrayAccess, too - and forbid write access there.