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.
Related
I'm writing a method that copies an object. Instead of manually setting each property manually, it would be more robust to just loop over the original object's properties...
//Booo
$new->name = $old->name;
$new->color = $old->color;
...
//Oh yeah...
foreach ($old as $prop=>$val){
$new->$prop = $val;
}
unset $new->id;
It appears that CakePHP entities cannot be iterated over in this way. I tried using $old->toArray(), which basically works... but has the drawback of converting all the associations to arrays also, which is screwing this up for me down stream.
How do I loop over the $old properties without converting all the data types?
Update:
Mark brought to my attention the existence of a __clone() method. Sounds like it does exactly what I need but I'm still figuring out how to use it.
You can use $entity->visualProperties()
foreach($old->visualProperties() as $property) {
if($new->has($property))
$new->set($property, $old->get($property));
After looking at this for a while, and discovering there is no __clone() function for entities, at least in 3.8, I have worked out how to do it, with the hint from DouglasSantos :
//Find out the entity classname
$classname = get_class($entity);
//Instanciate a new object of that class
$clone = new $classname;
//Use visibleProperties to clone it
foreach($entity->visibleProperties() as $property)
if($clone->has($property))
$clone->set($property, $entity->get($property));
Of course you could combine the first 2 lines into one line, but I have split it out for clarity.
UPDATE: I have discovered if you use the has->($property) check it will skip many of the fields. So the corrected answer is :
//Find out the entity classname
$classname = get_class($entity);
//Instanciate a new object of that class
$clone = new $classname;
//Use visibleProperties to clone it
foreach($entity->visibleProperties() as $property)
$clone->$property = $entity->$property;
It is actually much easier to use the Table Object:
// Assuming your model is called "Documents"
// If you are in the Controller, you can just use `$this->Documents`
instead of fetching the Table from the Registry
use Cake\ORM\TableRegistry;
$table = TableRegistry::getTableLocator()->get('Documents');
// newEntity() creates a new Entity from an array of data
$documentCopy = $table->newEntity(
// extract() extracts the given properties as an associative array
$document->extract(
// getVisible() will get all visible properties as an array
$document->getVisible()
)
);
When you implement the _toString method on a class, you are able to convert the object in string
$string =(string) $object
Is there an equivalent for converting in array
$array=(array) $object
From what I have tested, with this code, the attributes of the objet are transformed in index of the array, even if this object implement ArrayAccess.
I expected that casting an object with array access, I would obtain an array thith the same values I could access with the object
public class MyObject implements ArrayAccess{
private $values;
public function __construct(array $values){
$this->values=$values;
}
public function offsetSet($name,$value){
$this->values[$name]=$value;
}
//etc...
}
$myObject=new MyObject(array('foo'=>'bar');
$asArray=(array)$myObject;
print_r($asArray);
// expect array('foo'=>'bar')
// but get array('MyObjectvalues'=>array('foo'=>'bar'));
I also Notice that the native ArrayObject class has a the behavior I expected
No, there is no magic function to cast object as array.
ArrayObject is implemented with C and has weird specific behaviors.
Implement custom method asArray and use it.
Actually, it's impossible to write a general function:
/*
* #return array ArrayAccess object converted into an array
*/
function (ArrayAccess $arrayAccessObject): array { /* ... */ }
Why? Because ArrayAccess interface just gives a way to use $aa[/*argument*/] syntax, but does not give a way to iterate over all possible arguments.
We used to think that array has a finite number of keys. However ArrayAccess let us create objects having an infinite set of keys (note, the same concerns Traversable: i.e. prime numbers are "traversable").
For example, one can write a class, implementing ArrayAccess, that acts like a HTTP client with a cache (I'm not saying that it's a good idea; it's just an example). Then offsetExists($url) tells if a URL gives 200 or not, offsetGet($url) returns a content of a URL, offsetUnset($url) clears cached content, offsetSet throws a LogicException, 'cause setting a value makes no sense in this context.
// ...
if (empty($client['https://example.com/file.csv'])) {
throw new RuntimeException('Cannot download the file');
}
$content = $client['https://example.com/file.csv'];
// ...
Or maybe one wants to read/write/unset (delete) files with ArrayAccess.
Or maybe something like (set of even numbers is infinite):
$even = new EvenNumberChecker(); // EvenNumberChecker implements ArrayAccess
$even[2]; // true
$even[3]; // false
$even[5.6]; // throws UnexpectedValueException
isset($even[7.8]); // false
$even[0] = $value; // throws LogicException
ArrayAccess objects from academic examples above cannot be converted into finite arrays.
You can use json_decode and json_encode to get the most generic function for it:
public static function toArray(ArrayAccess $array): array
{
return json_decode(
json_encode($array),
true
);
}
I've read that it's better to have properties of a class be private, and use get/set methods to access/change them.
So I set up a class that way, but I want to be able to display the properties in an html table and I was going to use a separate htmlTable display class to do it.
I thought of 4 possibilities. Feel free to skip them if you already know the ideal way to do this.
Thank you.
Possibilities:
I can get the class fields using:
$class_Vars = get_class_vars($object_class);
$fields = $class_vars['fields'];
// But as far as iterating through each object, this doesn't work:
foreach($object_array as $current_object) {
foreach($current_object as $value) {
$html = '<td>' . $value . '</td>';
}
}
The values are private and inaccessible.
A possible solution that looks very clumsy and would probably be a debugging nightmare is:
foreach($fields as $value) {
$get_func = 'get' . ucwords($value); // e.g. $get_func = 'getId'
$current_value = $current_object->$get_func();
}
I think it would work but it doesn't sit right with me.
Interface.
Another possibility is to write in an htmlTable function into every class I want to do it. But that is a lot of code reuse.
Interface.
Or I could write in an export() function into every class that just outputs an array with property names and values. Then my htmlTable class can just handle those outputs.
Either make them public or make get/set methods.
You could also make a getAll() method that returns an associative array with all of your private variables.
So, i would like to implement something like this:
class Collection{
private $array;
public function add($object){
array_push($this->array, $object);
}
public function array(){
return $this->array;
}
}
class JustaClass{
public $myCollection;
public function __construct(){
$this->myCollection = new Collection();
}
}
$justAnObject = new JustaClass();
$justAnObject->myCollection->add(new SomeObject());
this works just fine, but i would like to work with it like i do in .Net, ie, when i want to refer to the collection Object, i would like to do it directly, like:
foreach($justAnObject->myCollection as $collectionItem)
and not like
foreach($justAnObject->myCollection->array() as $collectionItem)
Is there any way I can do this? maybe a magic method, or implementing an Iiterator-like interface?
thanks
Actually, this is what SplObjectStorage does, so no need to code anything:
The SplObjectStorage class provides a map from objects to data or, by ignoring data, an object Set. This dual purpose can be useful in many cases involving the need to uniquely identify objects.
It implements Countable, Iterator and ArrayAccess, so you can foreach it, access it with [] and use count on it. Like the description says it's a Set, so it contains no duplicate elements.
If you want to allow for duplicate elements, you can simply use ArrayIterator or ArrayObject. You can find additional Data Structures similar to the various .NET collections in
http://php.net/manual/en/spl.datastructures.php
IMO, there is no point in writing a custom class unless you also need to customize behavior of any of the options mentioned above.
Let your Collection class implement the Iterator or IteratorAggregate interfaces. There's also an ArrayIterator class, so it's really as easy as just returning an instance of that class:
class Collection implements IteratorAggregate {
private $array;
public function add($object){
array_push($this->array, $object);
}
/* required by IteratorAggregate */
public function getIterator() {
return new ArrayIterator($this->array);
}
}
You can then use your class in the following way:
$c = new Collection();
$c->add(1);
$c->add(2);
$c->add('we can even add strings');
foreach($c as $v) {
doSomething($v);
}
I have a custom class object in PHP named product:
final class product
{
public $id;
public $Name;
public $ProductType;
public $Category;
public $Description;
public $ProductCode;
}
When passing an object of this class to my Data Access Layer I need to cast the object passed into a type of the product class so I can speak to the properties within that function. Since type casting in PHP works only with basic types what is the best solution to cast that passed object?
final class productDAL
{
public function GetItem($id)
{
$mySqlConnection = mysql_connect('localhost', 'username', 'password');
if (!$mySqlConnection) { trigger_error('Cannot connect to MySql Server!'); return; }
mysql_select_db('databaseName');
$rs = mysql_query("SELECT * FROM tblproduct WHERE ID='$id';");
$returnObject = mysql_fetch_object($rs, 'product');
return $returnObject;
}
public function SaveItem($objectToSave, $newProduct = false)
{
$productObject = new product();
$productObject = $objectToSave;
echo($objectToSave->Name);
$objectToSave->ID;
}
}
Right now I am creating a new object cast as a type of product and then setting it equal to the object passed to the function. Is there a better way of accomplishing this task? Am I going about the wrong way?
EDITED FOR CLARITY - ADD FULL PRODCUTDAL CLASS
You don't need to cast the object, you can just use it as if it was a product.
$name = $objectToSave->Name;
I´m not sure what you are trying to achieve, but if $objectToSave is already of class product:
You can simply call $objectToSave->SaveItem() (assuming SaveItem() is part of the product class) and access it´s properties in the function like $this->Name, etc.;
In your code $productObject and $objectToSave will hold a reference to the same object.
Type casts in PHP are done like this:
$converted = (type) $from;
Note, that this won't work if the object types are not compatible (if for example $form happens to be a string or object of mismatching type).
But usual solution (called Active Record pattern, present for example in Zend Framework) is to have a base class for a database item called Row. Individual items (for example the class product from your sample) then inherit from this class.
Typical ZF scenario:
$table = new Product_Table();
$product = $table->find($productId); // load the product with $productId from DB
$product->someProperty = $newPropertyValue;
$product->Save(); // UPDATE the database
Which is IMO much better than your solution.
EDIT:
You can't cast between two unrelated objects, it is not possible.
If you want to use the DAL like this, skip the "product" object and go for simple associative array. You can enumerate over its members with foreach, unlike object's properties (you could use reflection, but that's overkill).
My recommendation: Go for the Active Record pattern (it is easy to implement with magic methods). It will save you a lot of trouble.
Currently, you are creating a new Product, then discarding it immediately (as its reference is replaced by $objectToSave.) You will need to copy its properties one by one, I regret.
foreach (get_object_vars($objectToSave) as $key => $value)
{
$product->$key = $value;
}
(If the properties of $objectToSave are private, you will need to a expose a method to_array() that calls get_object_vars($this).)