I'm making a class which needs to provide me with all the data from the MySQL-table "content".
I want to have my data returned as an object.
So far I managed to get one object returned, but i'd like to get an Object Collection with all my rows from the database returned.
<?
class ContentItem {
public $id;
public $title;
public $subtitle;
public $conent;
public $intro_length;
public $active;
public $start_date;
public $end_date;
public $views;
static function getContentItems() {
$query = "SELECT * FROM content";
$result = mysql_query($query)or die(mysql_error());
$item = new ContentItem();
while ($data = mysql_fetch_object($result)) {
$item = $data;
}
return $item;
}
}
For collections you need to create an object which implements Iterator interface. You can fill it with arrays, objects or whatever you want. Iterator makes sure you can use this collection in foreach loops after.
Furthermore, there's a mistake in your code. You are overwriting $item over and over again. You should create an array (or object with implemented Iterator, as I mentioned) which will be filled each cycle of while (as tandu wrote already).
Your loop keeps overwriting data with item, and new ContentItem() is overwritten immediately. If by "object collection" you mean "array," it's quite simple:
$items = array();
while ($data = mysql_fetch_object($result)) {
$items[] = $data;
}
return $items;
If you want to return your own custom object collection, then define the collection class and add $data to its collection each time (probably stored in an array as well).
The simple solution would be an array. I also assume you want a ContentItem made from each set of $data
$items = array();
while ($data = mysql_fetch_object($result)) {
$items[] = new ContentItem($data);
}
return $items;
If you later want to work with the items, you can use foreach
foreach ($items as $item) {
// do something with $item
}
Related
I have an object with 2 attributes: id and name (class name: MyObject):
class MyObject {
public id;
public name;
}
And I have an array "MyObjects" where each item is a MyObject instance.
I am looping through this array to display all the objects in one view (MVC Web Application).
In an other place (Outside of the view and the controller (Validation class)) I will need an array of just the ids of all objects. What I am doing now is just use a private method in the controller to loop through "MyObjects" again and put the ids in an array:
private function getMyObjectsIds($myObjects) {
$myObjectsIds = array();
foreach ($myObjects as $myObject) {
$myObjectsIds[] = $myObject->id;
}
return $myObjectsIds;
}
My Question:
Is there a better way to retrieve the ids of all the objects as an array?
I do not feel like this is the job of the controller and I would prefer to not save MyObjects in a new attribute of the model to just use the same method from the model.
Thanks
I would use array_map for it:
$myObjectsIds = array_map(function($item) {
return $item->id;
}, $myObjects);
http://php.net/manual/en/function.array-map.php
You can create your own "array" class where that logic is encapsulated.
For example:
class Collection
{
public $items;
public function __construct(array $items)
{
$this->items = $items;
}
public function lists($name)
{
return array_map(function ($item) use ($name) {
return $item->{$name};
}, $this->items);
}
// Other common methods
}
To get a list of ids.
$myObjects = new Collection([]);
$ids = $myObjects->lists('id');
This is not a full example, as you would also need to implement Serializable and Iterator interfaces so it behaves like an array.
I have a long running PHP daemon with a collection class that extends ArrayIterator. This holds a set of custom Column objects, typically less than 1000. Running it through the xdebug profiler I found my find method consuming about 35% of the cycles.
How can I internally iterate over the items in an optimized way?
class ColumnCollection extends \ArrayIterator
{
public function find($name)
{
$return = null;
$name = trim(strtolower($name));
$this->rewind();
while ($this->valid()) {
/** #var Column $column */
$column = $this->current();
if (strtolower($column->name) === $name) {
$return = $column;
break;
}
$this->next();
}
$this->rewind();
return $return;
}
}
Your find() method apparently just returns the first Column object with the queried $name. In that case, it might make sense to index the Array by name, e.g. store the object by it's name as the key. Then your lookup becomes a O(1) call.
ArrayIterator implements ArrayAccess. This means you can add new items to your Collection like this:
$collection = new ColumnCollection;
$collection[$someCollectionObject->name] = $someCollectionObject;
and also retrieve them via the square bracket notation:
$someCollectionObject = $collection["foo"];
If you don't want to change your client code, you can simply override offsetSet in your ColumnCollection:
public function offsetSet($index, $newValue)
{
if ($index === null && $newValue instanceof Column) {
return parent::offsetSet($newValue->name, $newValue);
}
return parent::offsetSet($index, $newValue);
}
This way, doing $collection[] = $column would automatically add the $column by name. See http://codepad.org/egAchYpk for a demo.
If you use the append() method to add new elements, you just change it to:
public function append($newValue)
{
parent::offsetSet($newValue->name, $newValue);
}
However, ArrayAccess is slower than native array access, so you might want to change your ColumnCollection to something like this:
class ColumnCollection implements IteratorAggregate
{
private $columns = []; // or SplObjectStorage
public function add(Column $column) {
$this->columns[$column->name] = $column;
}
public function find($name) {
return isset($this->data[$name]) ? $this->data[$name] : null;
}
public function getIterator()
{
return new ArrayIterator($this->data);
}
}
I replaced the iterator method calls with a loop on a copy of the array. I presume this gives direct access to the internal storage since PHP implements copy-on-write. The native foreach is much faster than calling rewind(), valid(), current(), and next(). Also pre-calculating the strtolower on the Column object helped. This got performance down from 35% of cycles to 0.14%.
public function find($name)
{
$return = null;
$name = trim(strtolower($name));
/** #var Column $column */
foreach ($this->getArrayCopy() as $column) {
if ($column->nameLower === $name) {
$return = $column;
break;
}
}
return $return;
}
Also experimenting with #Gordon's suggestion of using an array keyed on name instead of using the internal storage. The above is working well for a simple drop-in replacement.
I have an object that implements Iterator and holds 2 arrays: "entries" and "pages". Whenever I loop through this object, I want to modify the entries array but I get the error An iterator cannot be used with foreach by reference which I see started in PHP 5.2.
My question is, how can I use the Iterator class to change the value of the looped object while using foreach on it?
My code:
//$flavors = instance of this class:
class PaginatedResultSet implements \Iterator {
private $position = 0;
public $entries = array();
public $pages = array();
//...Iterator methods...
}
//looping
//throws error here
foreach ($flavors as &$flavor) {
$flavor = $flavor->stdClassForApi();
}
The reason for this is that sometimes $flavors will not be an instance of my class and instead will just be a simple array. I want to be able to modify this array easily regardless of the type it is.
I just tried creating an iterator which used:
public function ¤t() {
$element = &$this->array[$this->position];
return $element;
}
But that still did not work.
The best I can recommend is that you implement \ArrayAccess, which will allow you to do this:
foreach ($flavors as $key => $flavor) {
$flavors[$key] = $flavor->stdClassForApi();
}
Using generators:
Updating based on Marks comment on generators, the following will allow you to iterate over the results without needing to implement \Iterator or \ArrayAccess.
class PaginatedResultSet {
public $entries = array();
public function &iterate()
{
foreach ($this->entries as &$v) {
yield $v;
}
}
}
$flavors = new PaginatedResultSet(/* args */);
foreach ($flavors->iterate() as &$flavor) {
$flavor = $flavor->stdClassForApi();
}
This is a feature available in PHP 5.5.
Expanding upon Flosculus' solution, if you don't want to reference the key each time you use the iterated variable, you can assign a reference to it to a new variable in the first line of your foreach.
foreach ($flavors as $key => $f) {
$flavor = &$flavors[$key];
$flavor = $flavor->stdClassForApi();
}
This is functionally identical to using the key on the base object, but helps keep code tidy, and variable names short... If you're into that kind of thing.
If you implemented the iterator functions in your calss, I would suggest to add another method to the class "setCurrent()":
//$flavors = instance of this class:
class PaginatedResultSet implements \Iterator {
private $position = 0;
public $entries = array();
public $pages = array();
/* --- Iterator methods block --- */
private $current;
public function setCurrent($value){
$this->current = $value;
}
public function current(){
return $this->current;
}
//...Other Iterator methods...
}
Then you can just use this function inside the foreach loop:
foreach ($flavors as $flavor) {
$newFlavor = makeNewFlavorFromOldOne($flavor)
$flavors -> setCurrent($newFlavor);
}
If you need this function in other classes, you can also define a new iterator and extend the Iterator interface to contain setCurrent()
How do people usually turn mysqli results into objects?
I cannot find any examples with fetch_object() when using custom classes. Assume the following setup
class Test1 {
function __construct(array $data) { ... }
}
class Test2 {
function __construct(array $data) { ... }
}
Assume that I have multiple classes with the same constructors, how can I dynamically instantiate these classes?
I would imagine something like this:
function customQuery($query, $class) {
// Get some data by mysqli query, assume we get the data as $result object
...
// Now I would like to store the data in an array with instances of $class
$array = array();
while ($obj = $result->fetch_object($class, ???) {
$array[] = $obj;
}
// Return the array with instances of $class
return $array;
}
What do I use as arguments there for my question marks? I only know that both class constructors of Test1 and Test2 want an associative array as input (probably not the same length!).
In the end I simply want to do something like
$arr1 = customQuery('SELECT id, product FROM test1 LIMIT 10', 'Test1');
$arr2 = customQuery('SELECT id, name, address FROM test2 LIMIT 10', 'Test2');
Of course I would appreciate your input if you have better ideas to achieve my goal.
Take a look at "fetch_object'
You have a argument $class_name
From the docs:
The name of the class to instantiate, set the properties of and return. If not specified, a stdClass object is returned.
It will automaticly create an instance of the given class and it will set the properties. so no need to pass an array
http://www.php.net/manual/en/mysqli-result.fetch-object.php
A bit more explanation,
The fetch_object just fills the private properties etc (PHP Magic... i don't like it).
If your object has required parameters in the constructor but you want to fetch it from the database the fetch_object lets you define arguments for the constructor so it can be constructed instead of throwing warnings/errors.
class Test {
private $title;
public function __construct($required) {
}
}
$mysql->fetch_object('Test'); //will trigger errors/warnings
$mysql->fetch_object('Test', array(null)); //won't
What you could do is simpel fetch an array and construct the object
function query($query, $class) {
$data = $query->fetch_assoc();
$instance = new $class($data);
return $instance;
}
The fetch_object() method will return an instance of stdClass. If you specify a class name, however, it'll return an instance of that class and populate attributes based on the data returned. You do NOT need to specify all of your columns in the class unless you want to change their visibility (i.e. protected or private). Otherwise they'll default to public.
Also, it's a good idea to have a static method within the class you want to instantiate to separate concerns neatly. See:
class Class_Name
{
public static function customQuery($query)
{
$return = array();
if ($result = $mysqli->query($query)) {
// not passing a class name to fetch_object()
// will return an instance of stdClass
while ($obj = $result->fetch_object('Class_Name')) {
// add the fetched object (as an instance of the
// 'Class_Name' class in this case) to the return array.
$return[] = $result;
}
return $return;
}
// return false if no results found
return false;
}
}
Call the static method thus and you'll get an array of 'Class_Name' objects:
$results = Class_Name::customQuery($query);
If you're only querying for 1 result the above would be like this:
class Class_Name
{
public static function customQuery($query)
{
$return = array();
if ($result = $mysqli->query($query)) {
return $result->fetch_object('Class_name');
}
// return false if no results found
return false;
}
}
With this you'll get a single 'Class_Name' object.
Note, if you're using a namespaced class apply the fully qualified namespaced class name.
There is not much use for this function.
Better make your object's constructor to accept an array with settings and use like this
while ($row = $result->fetch_assoc($res) {
$array[] = new Foo($row);
}
also note that fetch_assoc/fetch_object isn't always available with prepared statements.
You use fetch_object on a result. You do while ($obj = $result->fetch_query()) { ... }
function customQuery($query) {
if ($result = $mysqli->query($query)) {
while ($obj = $result->fetch_object()) {
# do stuff.
}
} else {
# raise some error, query did not make it.
}
}
If you specify param, this is a class that will be instantiated to handle the results.
# $result->fetch_object('MyClass');
MyClass {
private $id;
public function __construct($id/* fields */) {
$this->id = $id; # say `id` is the only field
}
}
Here is the proper way to use it:
<?php
$res = $mysqli->query($q);
while($params = $res->fetch_assoc()):
endwhile;
$res->free();
$res = $mysqli->query($q);
while ($obj = $res->fetch_object('ClassName', array($params)):
$obj_list[] = $obj;
endwhile;
?>
mysqli::fetch_object() doesn't send the fields names and values to the constructor, it just creates an object with the attributes without passing by the setters. If you want to pass by the constructor, you must first recover the result and give it to him in an array in parameters.
So what's happening if you want to pass by the constructor? At first, fetch_object() will create the attributes then it will overwrite them passing by the constructor.
One of the patterns that I frequently run across when developing is trying to collect a column/attribute value from a collection of objects into an array. For example:
$ids = array();
foreach ($documents as $document) {
$ids[] = $document->name;
}
Am I the only one who runs into this? And does PHP have a way to solve this in fewer lines? I've looked but found nothing.
Since I use an MVC framework I have access to a BaseUtil class which contains common functions that don't really fit in any specific classes. One solution proposed by a co-worker is:
class BaseUtil
{
public static function collect($collection, $property) {
$values = array();
foreach ($collection as $item) {
$values[] = $item->{$property};
}
return $values;
}
}
Then I can just do:
$ids = BaseUtil::collect($documents, 'name');
Not too shabby. Anyone else have any other ideas? And am I crazy or does this seem like a problem that PHP should have solved a long time ago?
You can use array_map() function for this purpose:
function getName($obj) {
return $obj->name;
}
$documentsName = array_map("getName", $documents);
You might also consider the create_function() function for lambda functions if you don't want to create a getName() function in the global namespace.
In PHP 5.3 you might even do:
$documentsName = array_map(function ($obj) { return $obj->name; }, $documents);
You can do it easily with ouzo goodies
$names = array_map(Functions::extract()->name, $documents);
or with Arrays (from ouzo goodies)
$names = Arrays::map($documents, Functions::extract()->name);
You can even extract nested fields of methods calls or array access etc:
$names = Arrays::map($documents, Functions::extract()->getAuthor()->roles[0]);
Check out: http://ouzo.readthedocs.org/en/latest/utils/functions.html#extract
See also functional programming with ouzo (I cannot post a link).
another approach is to use "rich" Array objects, like those found in other languages
for example
class Ary extends ArrayObject
{
function pluck($key) {
$a = array();
foreach($this as $sub) $a[] = $sub[$key];
return new self($a);
}
function join($delim = ',') {
return implode($delim, (array) $this);
}
static function init($ary) {
return new self($ary);
}
}
echo
Ary::init(array(
array('foo', 'bar'), array('baz', 'quux')
))->pluck(1)->join();
One of PHP's weaknesses as a language is that it's not terribly expressive.
Where in languages like Ruby or Perl you could probably get this data with a single line of code, you generally need small algorithms like the one you posted to get the desired results.
I'd stick with what you have, but here's another approach just for the heck of it.
class BaseUtil
{
public static function collect($collection, $property)
{
array_walk( $collection, array( __CLASS__, 'reduceObject' ), $property );
return $collection;
}
public static function reduceObject( &$object, $index, $property )
{
$object = $object->{$property};
}
}
Thanks for the input guys. I guess I'm just going to use my coworker's solution:
class BaseUtil
{
public static function collect($collection, $property) {
$values = array();
foreach ($collection as $item) {
$values[] = $item->{$property};
}
return $values;
}
}
Loading a Magento collection, and than looping again in that collection so you can add your desired values into an array it's ineffective. The appropriate way to do this is using getColumnValues() method. This method will give you an array of values by specifying the column name.
Here is the appropriate way of doing this.
$collection =Mage::getModel('your/object')->getCollection()
->addFieldToSelect('customer_id');
$yourArray = $collection->getColumnValues('customer_id');
This will give you an array with all customer_id values you selected.