Doctrine orderBy annotation. Ordering Entity associations based on an associated Entity. - php

Say have the following entities with a Symfony app.
class List
{
/**
* #ORM\OneToMany(targetEntity="ListItem", mappedBy="list")
* #ORM\OrderBy({"category.title" = "ASC"})
*/
protected $listItems;
}
class ListItem
{
/**
* #ORM\ManyToOne(targetEntity="List", inversedBy="listItems")
*/
protected $list;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="listItems")
*/
protected $category;
}
class Category
{
/**
* #ORM\OneToMany(targetEntity="ListItem", mappedBy="cateogory")
*/
protected $listItems;
protected $title;
}
The orderBy argument, category.title unfortunately will not work in doctrine. My understanding is that the most common solution is to store an extra property on the ListItem Entity such as $categoryTitle and using this new field in the orderBy annotation. For example;
class List
{
/**
* #ORM\OneToMany(targetEntity="ListItem", mappedBy="list")
* #ORM\OrderBy({"categoryTitle" = "ASC"})
*/
protected $listItems;
}
class ListItem
{
// --
protected $categoryTitle
}
The problem with this approach is the extra overhead of keeping this $categoryTitle, up to date through set methods and/or listeners, and obviously the denormalization of database data.
Is there a method I can use to order this association with doctrine, without degrading the quality of my database?

To solve this issue, I added the following method to the abstractEntity that all of our entities extend, and therefore all entities can sort their collections.
The following code has hasn't under gone any tests, but it should be a good starting point for anyone that might have this issue in future.
/**
* This method will change the order of elements within a Collection based on the given method.
* It preserves array keys to avoid any direct access issues but will order the elements
* within the array so that iteration will be done in the requested order.
*
* #param string $property
* #param array $calledMethods
*
* #return $this
* #throws \InvalidArgumentException
*/
public function orderCollection($property, $calledMethods = array())
{
/** #var Collection $collection */
$collection = $this->$property;
// If we have a PersistentCollection, make sure it is initialized, then unwrap it so we
// can edit the underlying ArrayCollection without firing the changed method on the
// PersistentCollection. We're only going in and changing the order of the underlying ArrayCollection.
if ($collection instanceOf PersistentCollection) {
/** #var PersistentCollection $collection */
if (false === $collection->isInitialized()) {
$collection->initialize();
}
$collection = $collection->unwrap();
}
if (!$collection instanceOf ArrayCollection) {
throw new InvalidArgumentException('First argument of orderCollection must reference a PersistentCollection|ArrayCollection within $this.');
}
$uaSortFunction = function($first, $second) use ($calledMethods) {
// Loop through $calledMethods until we find a orderable difference
foreach ($calledMethods as $callMethod => $order) {
// If no order was set, swap k => v values and set ASC as default.
if (false == in_array($order, array('ASC', 'DESC')) ) {
$callMethod = $order;
$order = 'ASC';
}
if (true == is_string($first->$callMethod())) {
// String Compare
$result = strcasecmp($first->$callMethod(), $second->$callMethod());
} else {
// Numeric Compare
$difference = ($first->$callMethod() - $second->$callMethod());
// This will convert non-zero $results to 1 or -1 or zero values to 0
// i.e. -22/22 = -1; 0.4/0.4 = 1;
$result = (0 != $difference) ? $difference / abs($difference): 0;
}
// 'Reverse' result if DESC given
if ('DESC' == $order) {
$result *= -1;
}
// If we have a result, return it, else continue looping
if (0 !== (int) $result) {
return (int) $result;
}
}
// No result, return 0
return 0;
};
// Get the values for the ArrayCollection and sort it using the function
$values = $collection->getValues();
uasort($values, $uaSortFunction);
// Clear the current collection values and reintroduce in new order.
$collection->clear();
foreach ($values as $key => $item) {
$collection->set($key, $item);
}
return $this;
}
This method then could then be called somewhat like the below to solve the original question
$list->orderCollection('listItems', array('getCategory' => 'ASC', 'getASecondPropertyToSortBy' => 'DESC'))

Related

How to set _models and _keys for BaseDataProvider

<?php
/**
* #link http://www.yiiframework.com/
* #copyright Copyright (c) 2008 Yii Software LLC
* #license http://www.yiiframework.com/license/
*/
namespace yii\data;
use Yii;
use yii\base\Component;
use yii\base\InvalidParamException;
/**
* BaseDataProvider provides a base class that implements the [[DataProviderInterface]].
*
* #property integer $count The number of data models in the current page. This property is read-only.
* #property array $keys The list of key values corresponding to [[models]]. Each data model in [[models]] is
* uniquely identified by the corresponding key value in this array.
* #property array $models The list of data models in the current page.
* #property Pagination|boolean $pagination The pagination object. If this is false, it means the pagination
* is disabled. Note that the type of this property differs in getter and setter. See [[getPagination()]] and
* [[setPagination()]] for details.
* #property Sort|boolean $sort The sorting object. If this is false, it means the sorting is disabled. Note
* that the type of this property differs in getter and setter. See [[getSort()]] and [[setSort()]] for details.
* #property integer $totalCount Total number of possible data models.
*
* #author Qiang Xue <qiang.xue#gmail.com>
* #since 2.0
*/
abstract class BaseDataProvider extends Component implements DataProviderInterface
{
/**
* #var string an ID that uniquely identifies the data provider among all data providers.
* You should set this property if the same page contains two or more different data providers.
* Otherwise, the [[pagination]] and [[sort]] may not work properly.
*/
public $id;
private $_sort;
private $_pagination;
private $_keys;
private $_models;
private $_totalCount;
/**
* Prepares the data models that will be made available in the current page.
* #return array the available data models
*/
abstract protected function prepareModels();
/**
* Prepares the keys associated with the currently available data models.
* #param array $models the available data models
* #return array the keys
*/
abstract protected function prepareKeys($models);
/**
* Returns a value indicating the total number of data models in this data provider.
* #return integer total number of data models in this data provider.
*/
abstract protected function prepareTotalCount();
/**
* Prepares the data models and keys.
*
* This method will prepare the data models and keys that can be retrieved via
* [[getModels()]] and [[getKeys()]].
*
* This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before.
*
* #param boolean $forcePrepare whether to force data preparation even if it has been done before.
*/
public function prepare($forcePrepare = false)
{
if ($forcePrepare || $this->_models === null) {
$this->_models = $this->prepareModels();
}
if ($forcePrepare || $this->_keys === null) {
$this->_keys = $this->prepareKeys($this->_models);
}
}
/**
* Returns the data models in the current page.
* #return array the list of data models in the current page.
*/
public function getModels()
{
$this->prepare();
return $this->_models;
}
/**
* Sets the data models in the current page.
* #param array $models the models in the current page
*/
public function setModels($models)
{
$this->_models = $models;
}
/**
* Returns the key values associated with the data models.
* #return array the list of key values corresponding to [[models]]. Each data model in [[models]]
* is uniquely identified by the corresponding key value in this array.
*/
public function getKeys()
{
$this->prepare();
return $this->_keys;
}
/**
* Sets the key values associated with the data models.
* #param array $keys the list of key values corresponding to [[models]].
*/
public function setKeys($keys)
{
$this->_keys = $keys;
}
/**
* Returns the number of data models in the current page.
* #return integer the number of data models in the current page.
*/
public function getCount()
{
return count($this->getModels());
}
/**
* Returns the total number of data models.
* When [[pagination]] is false, this returns the same value as [[count]].
* Otherwise, it will call [[prepareTotalCount()]] to get the count.
* #return integer total number of possible data models.
*/
public function getTotalCount()
{
if ($this->getPagination() === false) {
return $this->getCount();
} elseif ($this->_totalCount === null) {
$this->_totalCount = $this->prepareTotalCount();
}
return $this->_totalCount;
}
/**
* Sets the total number of data models.
* #param integer $value the total number of data models.
*/
public function setTotalCount($value)
{
$this->_totalCount = $value;
}
/**
* Returns the pagination object used by this data provider.
* Note that you should call [[prepare()]] or [[getModels()]] first to get correct values
* of [[Pagination::totalCount]] and [[Pagination::pageCount]].
* #return Pagination|boolean the pagination object. If this is false, it means the pagination is disabled.
*/
public function getPagination()
{
if ($this->_pagination === null) {
$this->setPagination([]);
}
return $this->_pagination;
}
/**
* Sets the pagination for this data provider.
* #param array|Pagination|boolean $value the pagination to be used by this data provider.
* This can be one of the following:
*
* - a configuration array for creating the pagination object. The "class" element defaults
* to 'yii\data\Pagination'
* - an instance of [[Pagination]] or its subclass
* - false, if pagination needs to be disabled.
*
* #throws InvalidParamException
*/
public function setPagination($value)
{
if (is_array($value)) {
$config = ['class' => Pagination::className()];
if ($this->id !== null) {
$config['pageParam'] = $this->id . '-page';
$config['pageSizeParam'] = $this->id . '-per-page';
}
$this->_pagination = Yii::createObject(array_merge($config, $value));
} elseif ($value instanceof Pagination || $value === false) {
$this->_pagination = $value;
} else {
throw new InvalidParamException('Only Pagination instance, configuration array or false is allowed.');
}
}
/**
* Returns the sorting object used by this data provider.
* #return Sort|boolean the sorting object. If this is false, it means the sorting is disabled.
*/
public function getSort()
{
if ($this->_sort === null) {
$this->setSort([]);
}
return $this->_sort;
}
/**
* Sets the sort definition for this data provider.
* #param array|Sort|boolean $value the sort definition to be used by this data provider.
* This can be one of the following:
*
* - a configuration array for creating the sort definition object. The "class" element defaults
* to 'yii\data\Sort'
* - an instance of [[Sort]] or its subclass
* - false, if sorting needs to be disabled.
*
* #throws InvalidParamException
*/
public function setSort($value)
{
if (is_array($value)) {
$config = ['class' => Sort::className()];
if ($this->id !== null) {
$config['sortParam'] = $this->id . '-sort';
}
$this->_sort = Yii::createObject(array_merge($config, $value));
} elseif ($value instanceof Sort || $value === false) {
$this->_sort = $value;
} else {
throw new InvalidParamException('Only Sort instance, configuration array or false is allowed.');
}
}
/**
* Refreshes the data provider.
* After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again,
* they will re-execute the query and return the latest data available.
*/
public function refresh()
{
$this->_totalCount = null;
$this->_models = null;
$this->_keys = null;
}
}
The code above is the BaseDataProvider for yii2. My question is how i can set the _models and _keys in yii2? Which file do i need to change to link to that? Sorry i am quite new to yii. Please provide an example if possible thank you.
That what's You pasted here is abstract Yii2 class, which You should NEVER edit.
To use this thing i suggest You to read about ActiveDataProvider here: Docs
$query = Post::find()->where(['status' => 1]);
$provider = new ActiveDataProvider([
'query' => $query,
]);
Here's an example how to use it, first line defines data which will be used to populate ActiveDataProvider (it's a SQL query), and then You create ActiveDataProvider instance with query as config parameter.

Laravel Eloquent Case Sensitive Relationships

Hi SO I'm having real issues with some Laravel Eloquent relationships which I can only guess are being caused by a case-sensitive relation and I'm hoping somebody here can help!
Here are the models that I'm having the issues with:
class DeliveryManifestLines extends Eloquent
{
protected $table = 'manifests';
public function sapDelivery()
{
return $this->hasOne('Delivery', 'DocNum', 'sap_delivery');
}
}
class Delivery extends Eloquent
{
protected $connection = 'sap';
protected $table = 'ODLN';
protected $primaryKey = 'DocNum';
public function deliveryManifest() {
return $this->belongsTo('DeliveryManifest', 'DocNum', 'sap_delivery');
}
public function address()
{
return $this->hasOne('Address', 'Address', 'ShipToCode')->where('CardCode', $this->CardCode)->where('AdresType', 'S');
}
public function geolocation()
{
return $this->hasOne('GeoLocation', 'Address', 'ShipToCode')->where('CardCode', $this->CardCode)->where('AdresType', 'S')->where('Lat', '>', 0)->where('Lng', '>', 0);
}
}
class Address extends Eloquent
{
protected $connection = 'sap';
protected $table = 'CRD1';
protected $primaryKey = 'Address';
public function delivery() {
return $this->belongsTo('Delivery', 'Address', 'ShipToCode');
}
}
Here's the code in my controller that is supposed to fetch some of the above models from the DB.
$deliveries = DeliveryManifestLines::with('sapDelivery')->where('manifest_date', $date))->get();
foreach ($deliveries as $delivery) {
$delivery->sapDelivery->load('address');
}
I'm using the "->load('address)" line as no matter what I tried I could not get eager loading to work with "sapDelivery.address"
In 99% of cases the address is loaded successfully from the DB but I have come across one case in which I am experiencing an issue that I can only think is being caused by case-sensitivity.
Using Laravel DebugBar I can see that my application is executing the following query:
SELECT * FROM [CRD1] WHERE [CardCode] = 'P437' AND [AdresType] = 'S' AND [CRD1].[Address] IN ('The Pizza Factory (topping)')
When I dump the contents of $delivery->sapDelivery in this occurrence the address relation is NULL, however, when I paste the SQL statement into my DB console and execute it manually I get the expected row returned.
The only difference I can see between this one address and the thousands of others that are working is that there is a case difference between the Address fields:
In the CRD1 table the Address field for the effected/expected row is "The Pizza Factory (Topping)" but the eloquent relationship is using AND [CRD1].[Address] IN ('The Pizza Factory (topping)') to try and find it I'm aware that SQL is case-insensitive be default but I can't think of any other reason why this one row is behaving differently to the others.
Does anybody have any other ideas as to what could be causing this issue and suggest any possible solutions or confirm either way my theory of case sensitivity being the culprit.
Many thanks!
So After giving this problem little thought over the past few months I revisited the issue today and found some very useful code on laravel.io by somebody experiencing the same issue I found myself with.
I've built on MattApril's solution to provide the least hacky way I can think of to provide a way to offer case insensitive relationships in laravel.
To achieve this you need to add a few new classes which utilise the strtolower() function to create lower case keys which allows the isset() function used in the relationships to find differently cased but matching keys:
ModelCI.php (app\Models\Eloquent\ModelCI.php)
<?php
namespace App\Models\Eloquent;
use App\Models\Eloquent\Relations\BelongsToCI;
use App\Models\Eloquent\Relations\HasManyCI;
use App\Models\Eloquent\Relations\HasOneCI;
use Illuminate\Database\Eloquent\Model;
abstract class ModelCI extends Model
{
/**
* Define a one-to-many relationship.
*
* #param string $related
* #param string $foreignKey
* #param string $localKey
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function hasManyCI($related, $foreignKey = null, $localKey = null)
{
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related();
$localKey = $localKey ?: $this->getKeyName();
return new HasManyCI($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
}
/**
* Define a one-to-one relationship.
*
* #param string $related
* #param string $foreignKey
* #param string $localKey
* #return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function hasOneCI($related, $foreignKey = null, $localKey = null)
{
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
$localKey = $localKey ?: $this->getKeyName();
return new HasOneCI($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
}
/**
* Define an inverse one-to-one or many relationship.
*
* #param string $related
* #param string $foreignKey
* #param string $otherKey
* #param string $relation
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function belongsToCI($related, $foreignKey = null, $otherKey = null, $relation = null)
{
// If no relation name was given, we will use this debug backtrace to extract
// the calling method's name and use that as the relationship name as most
// of the time this will be what we desire to use for the relationships.
if (is_null($relation))
{
list(, $caller) = debug_backtrace(false, 2);
$relation = $caller['function'];
}
// If no foreign key was supplied, we can use a backtrace to guess the proper
// foreign key name by using the name of the relationship function, which
// when combined with an "_id" should conventionally match the columns.
if (is_null($foreignKey))
{
$foreignKey = snake_case($relation).'_id';
}
$instance = new $related;
// Once we have the foreign key names, we'll just create a new Eloquent query
// for the related models and returns the relationship instance which will
// actually be responsible for retrieving and hydrating every relations.
$query = $instance->newQuery();
$otherKey = $otherKey ?: $instance->getKeyName();
return new BelongsToCI($query, $this, $foreignKey, $otherKey, $relation);
}
}
BelongsToCI.php (app\Models\Eloquent\Relations\BelongsToCI.php)
<?php namespace App\Models\Eloquent\Relations;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class BelongsToCI extends BelongsTo {
/**
* Match the eagerly loaded results to their parents.
*
* #param array $models
* #param \Illuminate\Database\Eloquent\Collection $results
* #param string $relation
* #return array
*/
public function match(array $models, Collection $results, $relation)
{
$foreign = $this->foreignKey;
$other = $this->otherKey;
// First we will get to build a dictionary of the child models by their primary
// key of the relationship, then we can easily match the children back onto
// the parents using that dictionary and the primary key of the children.
$dictionary = array();
foreach ($results as $result)
{
$dictionary[strtolower($result->getAttribute($other))] = $result;
}
// Once we have the dictionary constructed, we can loop through all the parents
// and match back onto their children using these keys of the dictionary and
// the primary key of the children to map them onto the correct instances.
foreach ($models as $model)
{
if (isset($dictionary[strtolower($model->$foreign)]))
{
$model->setRelation($relation, $dictionary[strtolower($model->$foreign)]);
}
}
return $models;
}
}
HasManyCI.php (app\Models\Eloquent\Relations\HasManyCI.php)
<?php namespace App\Models\Eloquent\Relations;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\HasMany;
class HasManyCI extends HasMany {
/**
* Build model dictionary keyed by the relation's foreign key.
*
* #param \Illuminate\Database\Eloquent\Collection $results
* #return array
*/
protected function buildDictionary(Collection $results)
{
$dictionary = array();
$foreign = $this->getPlainForeignKey();
// First we will create a dictionary of models keyed by the foreign key of the
// relationship as this will allow us to quickly access all of the related
// models without having to do nested looping which will be quite slow.
foreach ($results as $result)
{
$dictionary[strtolower($result->{$foreign})][] = $result;
}
return $dictionary;
}
/**
* Match the eagerly loaded results to their many parents.
*
* #param array $models
* #param \Illuminate\Database\Eloquent\Collection $results
* #param string $relation
* #param string $type
* #return array
*/
protected function matchOneOrMany(array $models, Collection $results, $relation, $type)
{
$dictionary = $this->buildDictionary($results);
// Once we have the dictionary we can simply spin through the parent models to
// link them up with their children using the keyed dictionary to make the
// matching very convenient and easy work. Then we'll just return them.
foreach ($models as $model)
{
$key = strtolower( $model->getAttribute($this->localKey) );
if (isset($dictionary[$key]))
{
$value = $this->getRelationValue($dictionary, $key, $type);
$model->setRelation($relation, $value);
}
}
return $models;
}
}
HasOneCI.php (app\Models\Eloquent\Relations\HasOneCI.php)
<?php namespace App\Models\Eloquent\Relations;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\HasOne;
class HasOneCI extends HasOne {
/**
* Match the eagerly loaded results to their many parents.
*
* #param array $models
* #param \Illuminate\Database\Eloquent\Collection $results
* #param string $relation
* #param string $type
* #return array
*/
protected function matchOneOrMany(array $models, Collection $results, $relation, $type)
{
$dictionary = $this->buildDictionary($results);
// Once we have the dictionary we can simply spin through the parent models to
// link them up with their children using the keyed dictionary to make the
// matching very convenient and easy work. Then we'll just return them.
foreach ($models as $model)
{
$key = strtolower($model->getAttribute($this->localKey));
if (isset($dictionary[$key]))
{
$value = $this->getRelationValue($dictionary, $key, $type);
$model->setRelation($relation, $value);
}
}
return $models;
}
/**
* Build model dictionary keyed by the relation's foreign key.
*
* #param \Illuminate\Database\Eloquent\Collection $results
* #return array
*/
protected function buildDictionary(Collection $results)
{
$dictionary = array();
$foreign = strtolower($this->getPlainForeignKey());
// First we will create a dictionary of models keyed by the foreign key of the
// relationship as this will allow us to quickly access all of the related
// models without having to do nested looping which will be quite slow.
foreach ($results as $result)
{
$dictionary[$result->{$foreign}][] = $result;
}
return $dictionary;
}
}
To utilise the new classes you must define a relationship as so:
$this->belongsToCI('Model');
or
$this->hasManyCI('Model');
or
$this->hasOneCI('Model');
Eloquent uses internal associative arrays to map and link the related records to their parents and in cases with string foreign keys and difference in uppercase vs lowercase, some of the related records will not be mapped.
I recently came across the same problem and decided to wrap a solution in a composer package.
https://github.com/TishoTM/eloquent-ci-relations
After installing the composer package, you can simply use the trait inside the eloquent models without changing anything else on the relation methods within the model.
use \TishoTM\Eloquent\Concerns\HasCiRelationships;

Getting random results with Doctrine

I'm new to Doctrine and had a hard time deciding how to get random rows within it without creating custom DQL functions or using a native query.
The solution I have come up with is below, this is within a custom repository for an Entity. I'm hoping for some constructive criticism in regards to it, however it currently is working.
class MyRepository extends EntityRepository
{
/**
* #param int $numberOfResults
* #return array
*/
public function getRandomResults($numberOfResults = 1)
{
$maxID = $this->getMax();
$count = 0;
$randomNumberCounter = 0;
$randomNumbers = array();
while ($randomNumberCounter < $numberOfResults) {
$randomNumberCounter++;
$randomNumbers[] = $this->getRandom(0, $maxID);
}
$qb = $this->createQueryBuilder('r');
while ($count < $numberOfResults) {
$qb
->andWhere(
$qb->expr()->orX(
// the greater than is to account for holes within the primary key
$qb->expr()->gte("r.id", "?".$count),
/* the less than is in case we have a beginning database with a large disparity
between starting and ending ID */
$qb->expr()->lte("r.id", "?".$count)
)
);
$count++;
}
$result = $qb
->setParameters($randomNumbers)
->setMaxResults($numberOfResults)
// ensure we have no duplicates
->groupBy('r.id')
->getQuery();
return $result->getResult();
}
/**
* get the maximum ID that the table has
* #return mixed
* TODO: Create a Cron job to set this every 6 hours
*/
public function getMax()
{
$maxID = $this->createQueryBuilder('r')
->select('(MAX(r.id))')
->getQuery()
->getSingleScalarResult();
return $maxID;
}
/**
* #param $min
* #param $max
* #return int
*/
public function getRandom($min, $max)
{
return mt_rand($min, $max);
}
}

codeigniter is returning database row considered oop

im using Codeigniter 3.0 query builder, my question when ever my model return a user i'm returning a database row. not an object -its stdobject but not try object- is this anything related to oop practice ?
my auth model is simple
class user extend CI_MODEL{
funciton attempt($user,$pass){
//do validation and fetch user and compare pass etc...
$query = $this->db->get_where('users',$where);
return $query->result() //now this is my line of question
}
}
so i think this is nothing related to oop ? -or am i wrong ? - its just procedural code using classes for organization !.
so what is the correct way in oop manner ?
I have goan through many auth libraries for codeigntier to see how they do it, and all what i see is they save user row to an array variable in model. yet all users are still inside only 1 user object .
should i create an abstract class/interfaces for user object and pass database row to it every time i fetch a user before i save them to my Big ci_model ?
if so is this doable in codeigniter ? where would i put this abstract classes ?
I have done somthing like this, and yes I have created a model_row class to pass all data to in a array_walk style:
if ($qry = $this->db->get()) {
$res = $qry->result();
return $this->_instantiateRows($res);
}
Function _instantiateRows():
/**
* Get the row class name
* Checks if a class exists with the model name _Row
* #return string
*/
private function _getRowClass() {
$modelName = get_class($this);
return class_exists($modelName.'_Row') ? $modelName.'_Row' : 'Model_Row';
}
/**
* Formats results into model row classes
* #param array $results
* #return array
*/
protected function _instantiateRows($results) {
$rowClass = $this->_getRowClass();
$self = $this;
array_walk($results,function(&$row,$k) use ($rowClass, $self) {
$row = new $rowClass($row,$self,$k);
});
return $results;
}
Then a row class:
/**
* Model row class
* Acts as a baseclass and a fallback class for database rows
* Implements standard methods, for saving, getting the ID, and setting field
* values.
* #property $_model MY_Model
* #property $_key Original Array key
* #property $_ID_FIELD name of the id field
*/
class Model_Row
{
protected $_isNew = True;
protected $_model = False;
protected $_key = False;
protected $_ID_FIELD = 'id';
protected $_ID_ORIGINAL = False;
/**
* C'tor
* Sets up the object with data from the row
* #param object $data
* #param object $model
* #param int $key
* #param string $id_field
*/
public function __construct($data,$model,$key=False) {
$this->_key = $key;
// If no key is specified then it must be new / Not from database
if ($this->_key !== False)
$this->_isNew = False;
$data = (array)$data;
$this->_model = $model;
$this->_ID_FIELD = $model->idField;
$this->set($data);
// Ensure ID Field is set.
$idF = $this->_ID_FIELD;
if (!isset($this->$idF))
$this->$idF = null;
}
/**
* Get the ID field
* ID Field could be named differently for each model, this is an easy
* shortcut to it.
* #param string $setTo - set the id to this value
* #return string
*/
public function id($setTo=False) {
$idF = $this->_ID_FIELD;
if ($setTo !== False) {
if ($this->_ID_ORIGINAL === False && !$this->_isNew)
$this->_ID_ORIGINAL = $this->$idF;
$this->set($idF,$setTo);
}
return $this->$idF !== null ? (string)$this->$idF : False;
}
/**
* Save this row
* #return bool
*/
public function save() {
$wheres = array();
if (!$this->_isNew) {
$idF = $this->_ID_FIELD;
$wheres[$idF] = $this->_ID_ORIGINAL ?: $this->id();
}
$res = $this->_model->set($this,$wheres);
if ($this->id() === False)
$this->id($this->_model->insertID());
// Reset the original id field
$this->_ID_ORIGINAL = False;
$this->_isNew = False;
if ($res)
return $this;
return False;
}
/**
* Set object properties
* can be passed by array field => value
* #param mixed $field
* #param mixed $value
* #return null
*/
public function set($field,$value=False) {
if ((is_array($field) || is_object($field)) && $value === False) {
if (is_object($field))
$field = (array)$field;
foreach($field as $f => $v)
$this->set($f,$v);
}
else {
if (method_exists($this, 'set_'.$field))
call_user_func(array($this,'set_'.$field), $value);
else
$this->$field = $value;
}
}
}
The point of the _getRowClass is to check for a class called model_name_row if this exists, then instantiate the data to this class, otherwise fall back to the baseclass model_row
There are some other things your model will need too, because the row class will be passed the model, so your model will need a public $idField='id' , and then this function can be usefull on your model:
/**
* Create a new record using the model row class
* #param mixed $data
* #return Model_Row
*/
public function newRow($data=array()) {
$rowClass = $this->_getRowClass();
return new $rowClass($data,$this);
}
So you can do $newRow = $this->Model->newRow($data) which will create a new row, then can call $newRow->save() and other methods if set...
* EDIT
Also to point out, I use $this->_model->set($this, $wheres) on the row class, this is because I have defined a baseclass with a public setter:
/**
* Setter
* #param mixed $data object or array
* #param mixed $wheres object or array query
* #return bool
*/
public function set($data,$wheres=array()) {
if (!$this->checkTableSet())
return False;
if (empty($wheres)) {
return $this->db->insert($this->table,$data);
}
else {
$this->db->where($wheres);
return $this->db->update($this->table,$data);
}
}
$this->table is a model variable with the table name, e.g. protected $table='users'; and the function checkTableSet() simply checks whether this has been set and not empty..

How php iterator class works ? (Iterate through zend table row set)

I have object which contains 100 records. I want to iterate through it and delete all the data in the object.
How can I do this in PHP iterator class ?
(Object is ZEND table row set object)
(Here delete means we are just making delete_flag in the database to 1. Data won't be deleted physically from the database.)
Eg:
$zendTableRowSetObject->list[0]->delete_flag = 1
$zendTableRowSetObject->list[2]->delete_flag = 1
$zendTableRowSetObject->list[3]->delete_flag = 1
$zendTableRowSetObject->save();
->save() is the Zend function, this will update the object which used to calls this method.
(Other than this any other method http://php.net/manual/en/language.oop5.iterations.php)
(Without using foreach loop is there any way to do it ?)
Give me some examples .
Here is my iterator class
class PersonListIter implements Iterator
{
protected $_PersonList;
/**
* Index of current entries
* It's used for iterator
* #var integer
*/
protected $_entryIndex = 0;
/**
* Entries data sets
* #var array
*/
protected $_entries;
/*
* Initialization of data.
*
* #params Zend_Db_Table_Rowset $list Row Object
* #return null
*/
public function __construct ( $list )
{
$this->_PersonList = $list;
$this->_entryIndex = 0;
$this->_entries = $list->getCount();
}
/*
* Iterator interface method to rewind index
* #return null
*/
public function rewind()
{
$this->_entryIndex = 0;
}
/*
* Iterator interface method to return Current entry
* #return Zend_Db_Table_Row Current Entry
*/
public function current()
{
return $this->_PersonList->getElement($this->_entryIndex);
}
/*
* Iterator interface method to return index of current entry
* #return int Current Entry Index
*/
public function key()
{
return $this->_entryIndex;
}
/*
* Iterator interface method to set the next index
* #return null
*/
public function next()
{
$this->_entryIndex += 1;
}
/*
* Iterator interface method to validate the current index
* #return enum 0/1
*/
public function valid()
{
return (0 <= $this->_entryIndex && $this->_entryIndex < $this->entries)?1:0;
}
} // class PersonListIter
$zendTableRowSetObject is the PersonList object in the iterator class
You cannot delete all of them at once, you have to iterate through (using foreach or while in conjunction with next()) to delete them.
While surfing I have found the following link which might interest you. This explains the use of implement iterator pattern in PHP in an nice way. >> http://www.fluffycat.com/PHP-Design-Patterns/Iterator/

Categories