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.
Related
In perl I'm used to doing
my $foo = new WhatEver( bar => 'baz' );
and now I'm trying to figure out if PHP objects can ever be constructed this way. I only see this:
my $foo = new WhatEver();
$foo->{bar} = 'baz';
is it possible to do it in one step?
You can lay out your constructor as follows:
class MyClass {
public function __construct($obj=null) {
if ($obj && $obj instanceof Traversable || is_array($obj)) {
foreach ($obj as $k => $v) {
if (property_exists($this,$k)) {
$this->{$k} = $v;
}
}
}
}
}
This has a serie of drawbacks:
This is inefficient
The variables you create will not show up on any doc software you use
This is the open door to all forms of slackery
However, it also presents the following benefits:
This can be extended pretty safely
It allows you to lazy-implement variables
It also allows you to set private variables, provided that you know their names. It is pretty good in that respect if not abused.
The parameters passed in the parentheses (which can be omitted, by the way, if there aren't any) go to the constructor method where you can do whatever you please with them. If a class is defined, for example, like this:
class WhatEver
{
public $bar;
public function __construct($bar)
{
$this -> bar = $bar;
}
}
You can then give it whatever values you need.
$foo = new WhatEver('baz');
There are a few ways to accomplish this, but each has its own drawbacks.
If your setters return an instance of the object itself, you can chain your methods.
my $foo = new WhatEver();
$foo->setBar("value")->setBar2("value2");
class WhatEver
{
public $bar;
public $bar2;
public function setBar($bar)
{
$this->bar = $bar;
return $this;
}
public function setBar2($bar2)
{
$this->bar2 = $bar2;
return $this;
}
}
However, this doesn't reduce it to one step, merely condenses every step after instantiation.
See: PHP method chaining?
You could also declare your properties in your constructor, and just pass them to be set at creation.
my $foo = new WhatEver($bar1, $bar2, $bar3);
This however has the drawback of not being overtly extensible. After a handful of parameters, it becomes unmanageable.
A more concise but less efficient way would be to pass one argument that is an associative array, and iterate over it setting each property.
The implicit assumption here is that objects have meaningful, presumably public, properties which it is up to the calling code to provide values for. This is by no means a given - a key aspect of OOP is encapsulation, so that an object's primary access is via its methods.
The "correct" mechanism for initialising an object's state is its constructor, not a series of property assignments. What arguments that constructor takes is up to the class definition.
Now, a constructor might have a long series of named parameters, so that you could write $foo = new WhatEver(1, "hello", false, null) but if you want these to act like options, then it could take a single hash - in PHP terms, an Array - as its argument.
So, to answer the question, yes, if your constructor is of the form function __construct(Array $options) and then iterates over or checks into $options. But it's up to the constructor what to do with those options; for instance passing [ 'use_safe_options' => true ] might trigger a whole set of private variables to be set to documented "safe" values.
As of PHP 5.4 (which introduced [ ... ] as an alternative to array( ... )), it only takes a few more character strokes than the Perl version:
$foo = new WhatEver( ['bar' => 'baz'] );
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.
I've created a class that keeps some information in its attributes. It contains add() method that adds a new set of information to all of the present in this class attributes.
I'd like its objects to behave like array offsets. For example, calling:
$obj = new Class[0];
would create the object containing the first set of information.
I'd also like to use foreach() loop on that class.
The changes of attributes should be denied from outside of the class, but I should have access to them.
Is that possible?
What you need is ArrayObject it implements IteratorAggregate , Traversable , ArrayAccess , Serializable , Countable altogether
Example
echo "<pre>";
$obj = new Foo(["A","B","C"]);
foreach ( $obj as $data ) {
echo $data, PHP_EOL;
}
echo reset($obj) . end($obj), PHP_EOL; // Use array functions on object
echo count($obj), PHP_EOL; // get total element
echo $obj[1] ; // you can get element
$obj[0] = "D"; // Notice: Sorry array can not be modified
Output
A
B
C
AC
3
B
Class Used
class Foo extends ArrayObject {
public function offsetSet($offset, $value) {
trigger_error("Sorry array can not be modified");
}
}
This is how you can create multiple instance with different constructor values.
$objConfig = array(
array('id'=>1 , 'name'=>'waqar') ,
array('id'=>2 , 'name'=>'alex')
);
$objects = array();
for($i=0; $i<count($objConfig) ; $i++)
{
$objects[$i] = new ClassName($objConfig[$i]);
}
You need to implement ArrayAccess interface, examples are pretty straightforward.
Anyway I really discourage you from mixing classes and array behaviour for bad design purposes: array-wise accessing should be used just to keep syntax more concise.
Take full advantage of classes, magic methods, reflection: there's a bright and happy world out there, beyond associative arrays.
In this case, why do you not just have an array of your class instances? A very simple example:
/**
* #var MyClass[]
*/
$myClasses = array();
$myClasses[] = new myClass();
Or alternatively use one of the more specialised SPL classes, here: http://php.net/manual/en/book.spl.php, such as SplObjectStorage (I haven't had a need for this, but it looks like it might be what you need)
Finally, you could roll your own, by simply creating a class that extends ArrayAccess and enforces you class type?
It really depends on what you need, for the vast majority of cases I would rely on storing classes in an array and enforcing any business logic in my model (so that array values are always the same class). This may be less performant, but assuming you're making a web app it is highly unlikely to be an issue.
Am I correct in thinking that IteratorAggregate only provides array-like read access to an object? If I need to write to the object-as-array, then I need to use Iterator?
Demonstration of an IteratorAggregate object causing a fatal error when trying to add a new element follows:
<?
class foo implements IteratorAggregate {
public $_array = array('foo'=>'bar', 'baz'=>'quux');
public function getIterator() {
return new ArrayIterator($this->_array);
}
} // end class declaration
$objFoo = & new foo();
foreach ( $objFoo as $key => $value ) {
echo "key: $key, value: $value\n";
}
$objFoo['reeb'] = "roob";
foreach ( $objFoo as $key => $value ) {
echo "key: $key, value: $value\n";
}
$ php test.php
key: foo, value: bar
key: baz, value: quux
Fatal error: Cannot use object of type foo as array in /var/www/test.php on line 20
Call Stack:
0.0009 57956 1. {main}() /var/www/test.php:0
For what it's worth, given your sample class, you could have just made use of the ArrayObject (which implements IteratorAggregate and ArrayAccess like we want).
class Foo extends ArrayObject
{
public function __construct()
{
parent::__construct(array('foo' => 'bar', 'baz' => 'quux'));
}
}
$objFoo = new Foo();
foreach ( $objFoo as $key => $value ) {
echo "$key => $value\n";
}
echo PHP_EOL;
$objFoo['foo'] = 'badger';
$objFoo['baz'] = 'badger';
$objFoo['bar'] = 'badger';
foreach ( $objFoo as $key => $value ) {
echo "$key => $value\n";
}
With the output being, as expected:
foo => bar
baz => quux
foo => badger
baz => badger
bar => badger
Ok, now that I better understand what you mean, let me try to explain here.
Implementing an iterator (either via the Iterator or IteratorAggregate interface) will only add functionality when iterating. What that means, is it only affects the ability to use foreach. It doesn't change or alter any other use of the object. Neither of which directly support "writing" to the iterator. I say directly, since you can still write to the base object while iterating, but not inside of the foreach.
That means that:
foreach ($it as $value) {
$it->foo = $value;
}
Will work fine (since it's writing to the original object).
While
foreach ($it as &$value) {
$value = 'bar';
}
Most likely won't work since it's trying to write through the iterator (which isn't directly supported. It may be able to be done, but it's not the core design).
To see why, let's look at what's going on behind the scenes with that foreach ($obj as $value). It basically is identical to:
For Iterator classes:
$obj->rewind();
while ($obj->valid()) {
$value = $obj->current();
// Inside of the loop here
$obj->next();
}
For IteratorAggregate classes:
$it = $obj->getIterator();
$it->rewind();
while ($it->valid()) {
$value = $it->current();
// Inside of the loop here
$it->next();
}
Now, do you see why writing isn't supported? $iterator->current() does not return a reference. So you can't write to it directly using foreach ($it as &$value).
Now, if you wanted to do that, you'd need to alter the iterator to return a reference from current(). However, that would break the interface (since it would change the methods signature). So that's not possible. So that means that writing to the iterator is not possible.
However, realize that it only affects the iteration itself. It has nothing to do with accessing the object from any other context or in any other manor. $obj->bar will be exactly the same for an iterator object as it is for a non iterator object.
Now, there comes a difference between the Iterator and IteratorAggregate. Look back at the while equivalencies, and you may be able to see the difference. Suppose we did this:
foreach ($objFoo as $value) {
$objFoo->_array = array();
print $value . ' - ';
}
What would happen? With an Iterator, it would only print the first value. That's because the iterator operates directly on the object for each iteration. And since you changed what the object iterates upon (the internal array), the iteration changes.
Now, if $objFoo is an IteratorAggregate, it would print all the values that existed at the start of iteration. That's because you made a copy of the array when you returned the new ArrayIterator($this->_array);. So you can continue to iterate over the entire object as it was at the start of iteration.
Notice that key difference. Each iteration of a Iterator will be dependent upon the state of the object at that point in time. Each iteration of a IteratorAggregate will be dependent upon the state of the object at the start of iteration.* Does that make sense?
Now, as for your specific error. It has nothing to do with iterators at all. You're trying to access (write actually) a variable outside of the iteration. So it doesn't involve the iterator at all (with the exception that you're trying to check the results by iterating).
You're trying to treat the object as an array ($objFoo['reeb'] = 'roob';). Now, for normal objects, that's not possible. If you want to do that, you need to implement the ArrayAccess interface. Note that you don't have to have an iterator defined in order to use that interface. All it does is provide the ability to access specific elements of an object using an array like syntax.
Note I said array like. You can't treat it like an array in general. sort functions will never work. However, there are a few other interfaces that you can implement to make an object more array like. One is the Countable interface which enables the ability to use the count() function on an object (count($obj)).
For an example in the core of combining all of these into one class, check out the ArrayObject class. Each interface in that list provides the ability to use a specific feature of the core. The IteratorAggregate provides the ability to use foreach. The ArrayAccess provides the ability to access the object with an array-like syntax. The Serializable interface provides the ability to serialize and deserialize the data in a specific manor. The Countable interface allows for counting the object just like an array.
So those interfaces don't affect the core functionality of the object in any way. You can still do anything with it that you could do to a normal object (such as $obj->property or $obj->method()). What they do however is provide the ability to use the object like an array in certain places in the core. Each provides the functionality in a strict scope (Meaning that you can use any combination of them that you would like. You don't need to be able to access the object like an array to be able to count() it). That's the true power here.
Oh, and assigning the return value of new by reference is deprecated, so there's no need to do that...
So, as for your specific problem, here's one way around it. I simply implemented the ArrayAccess interface into your class so that $objFoo['reeb'] = 'roob'; will work.
class foo implements ArrayAccess, IteratorAggregate {
public $_array = array('foo'=>'bar', 'baz'=>'quux');
public function getIterator() {
return new ArrayIterator($this->_array);
}
public function offsetExists($offset) {
return isset($this->_array[$offset]);
}
public function offsetGet($offset) {
return isset($this->_array[$offset]) ? $this->_array[$offset] : null;
}
public function offsetSet($offset, $value) {
$this->_array[$offset] = $value;
}
public function offsetUnset($offset) {
if (isset($this->_array[$offset]) {
unset($this->_array[$offset]);
}
}
}
Then, you can try your existing code:
$objFoo = & new foo();
foreach ( $objFoo as $key => $value ) {
echo "key: $key, value: $value\n";
}
$objFoo['reeb'] = "roob";
foreach ( $objFoo as $key => $value ) {
echo "key: $key, value: $value\n";
}
And it should work fine:
key: foo, value: bar
key: baz, value: quux
key: foo, value: bar
key: baz, value: quux
key: reeb, value: roob
You could also "fix" it by changing the $objFoo->_array property directly (just like regular OOP). Simply replace the line $objFoo['reeb'] = 'roob'; with $objFoo->_array['reeb'] = 'roob';. That'll accomplish the same thing....
Note that this is the default behavior. You could hack together an inner iterator (the iterator returned by IteratorAggreagate::getIterator) that does depend upon the original objects state. But that's not how it's typically done
What I would like to do is have a static factory function that you can give a series of attributes and it returns an object that is of a previously undeclared class that extends a known class.
Basically:
<?php
class foo {
public $a;
function increment($b = 1){
$this->a += $b;
}
}
function factory($name, $a){
//returns an object of class $name that extends foo with $this->a set to $a
}
so that if I write the code:
<?php
$bar = factory("bar",12);
$bar->increment(5);
print_r($bar);
if(is_a($bar, "foo")){
echo "is a Foo";
}
$moo = factory("moo", 4);
$moo->increment();
print_r($moo);
if(is_a($moo, "foo")){
echo "is a Foo";
}
I get: [edit]
bar Object
(
[a] => 17
)
is a Foo
moo Object
(
[a] => 5
)
is a Foo
But I don't know where to start looking for the commands necessary to do this. I think that in my factory function I need to somehow declare that the value of $name extends parent class but makes no changes to it, then constructs a new $name. That way it has all the functionality of the parent class, just a different type.
Check out the PHP reflection API has the methods you need to extract and build the new class there but, how to go about doing it and then creating an instance of it im not sure of. I do know its possible though because im pretty sure this is how Mocking works in PHPUnit. You might also want to look at the various Mock object related classes in PHPUnit to get some ideas as well.
That said unless you are actually adding/overloading methods, why would you even want to do this? Why not just use a property in the object or use a an interface? Whats the goal here?