私はそれが最善の方法だかどうかわからないんだけど、これは私の抽象マッパーです:
<?php
abstract class Zf_Model_DbTable_Mapper
{
protected $_db;
protected $_dbTable = null;
protected $_systemLogger = null;
protected $_userLogger = null;
public function __construct()
{
$this->_systemLogger = Zend_Registry::get('systemLogger');
$this->_userLogger = Zend_Registry::get('userLogger');
// Set the adapter
if(null !== $this->_dbTable)
{
$tableName = $this->_dbTable;
$this->_db = $this->$tableName->getAdapter();
}
}
public function __get($value)
{
if(isset($this->$value))
{
return $this->$value;
}
$dbTable = 'Model_DbTable_' . $value;
$mapper = 'Model_' . $value;
if(class_exists($dbTable))
{
return new $dbTable;
}
elseif(class_exists($mapper))
{
return new $mapper;
}
else
{
throw new Exception("The property, DbTable or Mapper \"$value\" doesn't exists");
}
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function getById($id)
{
$resource = $this->getDefaultResource();
$id = (int)$id;
$row = $resource->fetchRow('id =' . $id);
if (!$row) {
throw new Exception("Count not find row $id");
}
return $row;
}
public function getAll()
{
$resource = $this->getDefaultResource();
return $resource->fetchAll()->toArray();
}
public function save(Zf_Model $Model)
{
$dbTable = $this->getDefaultResource();
$data = $Model->toArray();
if(false === $data) return false;
if(false === $Model->isNew())
{
if(1 == $dbTable->update($data, 'id =' . (int)$Model->getId()))
{
return $Model;
}
}
else
{
$id = $dbTable->insert($data);
if($id)
{
$Model->setId($id);
return $Model;
}
}
return false;
}
public function remove($id)
{
return $this->getDefaultResource()->delete('id =' . (int) $id);
}
protected function getDefaultResource()
{
if(empty($this->_dbTable))
{
throw new Exception('The $_dbTable property was not set.');
}
$classname = 'Model_DbTable_' . $this->_dbTable;
if(!class_exists($classname))
{
throw new Exception("The Model_DbTable_\"$classname\" class was not found.");
}
return new $classname;
}
protected function getDefaultModel()
{
return current($this->_models);
}
protected function getResources()
{
return $this->_resources;
}
}
そして、これが私の実装マッパーのための1つである:
<?php
class Model_TwitterPostsMapper extends Zf_Model_DbTable_Mapper
{
/*
* Data Source
* @var string Zend_Db_Table name
*/
protected $_dbTable = 'TwitterPosts';
public function recordExists($Item)
{
$row = $this->TwitterPosts->fetchRow($this->TwitterPosts->select()->where('status_id =?', $Item->getSource()->getStatusId()));
if($row)
{
return $row->id;
}
return false;
}
public function getLastUpdate($options)
{
$select = $this->TwitterPosts->select()
->setIntegrityCheck(false)
->from(array('t' => 'twt_tweets'), 't.created_at')
->join(array('u' => 'twt_users'), 't.user_id = u.id', '')
->order('t.created_at DESC');
if($options['user_id'])
{
$select->where("t.user_id = ?", $options['user_id']);
}
if($options['terms'])
{
if(is_array($options['terms']))
{
$condition = '';
foreach($options['terms'] as $i => $term)
{
$condition .= ($i > 0) ? ' OR ' : '';
$condition .= $this->getAdapter()->quoteInto('content LIKE ?',"%$term%");
}
if($condition)
{
$select->where($condition);
}
}
}
return $this->TwitterPosts->fetchRow($select)->created_at;
}
public function getSinceId($term = null)
{
$select = $this->TwitterPosts->select()->setIntegrityCheck(false)
->from('twt_tweets_content', 'status_id')
->where('MATCH(content) AGAINST(? IN BOOLEAN MODE)', "$term")
->order('status_id ASC')
->limit(1);
//echo $select; exit;
$tweet = $this->TwitterPosts->fetchRow($select);
if(null !== $tweet) return $tweet->status_id;
return 0;
}
public function getAllByStatusId($statuses_id)
{
$select = $this->TwitterPosts->select()
->setIntegrityCheck(false)
->from(array('t' => 'twt_tweets'), array('t.id', 't.user_id', 't.status_id','t.user_id'))
->join(array('u' => 'twt_users'), 't.user_id = u.id', array('u.screen_name', 'u.profile_image'))
->where('status_id IN(?)', $statuses_id);
$rows = $this->TwitterPosts->fetchAll($select);
$Posts = array();
foreach($rows as $row)
{
// Here we populate the models only with the specific method return data
$data = $row->toArray();
$Post = new Model_TwitterPost($data['id']);
$Post->populate($data);
$User = new Model_TwitterUser($data['user_id']);
$User->populate($data);
$Post->setUser($User);
$Posts[] = $Post;
}
return $Posts;
}
public function getAllSince($since_id)
{
$select = $this->TwitterPosts->select()
->setIntegrityCheck(false)
->from(array('t' => 'twt_tweets'), array('t.status_id','t.user_id'))
->join(array('u' => 'twt_users'), 't.user_id = u.id', array('u.screen_name', 'u.profile_image'))
->where('status_id > ?', $since_id)
->order('t.datetime DESC');
$rows = $this->TwitterPosts->fetchAll($select);
$Posts = array();
foreach($rows as $row)
{
// Here we populate the models only with the specific method return data
// TODO: This is not a truly lazy instatiation, since there's no way to get the not setted properties
$data = $row->toArray();
$Post = new Model_TwitterPost($data);
$User = new Model_TwitterUser($data);
$Post->setUser($User);
$Posts[] = $Post;
}
return $Posts;
}
public function getTotalRatedItems($options)
{
$options = $this->prepareOptions($options);
$select = $this->TwitterPosts->select()
->setIntegrityCheck(false)
->from(array('t' => 'twt_tweets'), array('COUNT(DISTINCT t.id) AS total','r.rate'))
->join(array('u' => 'twt_users'), 't.user_id = u.id', '')
->join(array('r' => 'twt_tweets_rate'), 't.id = r.tweet_id', array('r.rate'))
->group('r.rate')
->order('t.datetime DESC');
$select = $this->prepareSelect($select, $options);
$rates = $this->TwitterPosts->fetchAll($select)->toArray();
$itemsRated = array('Green' => 0, 'Yellow' => 0, 'Orange' => 0, 'Red' => 0, 'Gray' => 0);
foreach ($rates as $rate)
{
$itemsRated[$rate['rate']] = $rate['total'];
}
return $itemsRated;
}
public function getUsersActivity($options)
{
$options = $this->prepareOptions($options);
$select = $this->TwitterPosts->select()
->setIntegrityCheck(false)
->from(array('t' => 'twt_tweets'), array('COUNT(DISTINCT t.id) AS total','DATE(t.datetime) AS datetime'))
->join(array('u' => 'twt_users'), 't.user_id = u.id', '')
->joinLeft(array('r' => 'twt_tweets_rate'), 't.id = r.tweet_id', '')
->group('t.user_id')
->order('t.datetime DESC');
$select = $this->prepareSelect($select, $options);
$activity = $this->TwitterPosts->fetchAll($select)->toArray();
return $activity;
}
public static function prepareOptions($options)
{
if(!is_array($options))
{
$options = array();
}
date_default_timezone_set('America/Sao_Paulo');
if(Zend_Date::isDate($options['start_date']))
{
$date = new Zend_Date($options['start_date']);
$date->setTime('00:00:00');
$date->setTimezone('UTC');
$options['start_date'] = $date->toString('yyyy-MM-dd HH:mm:ss');
}
if(Zend_Date::isDate($options['end_date']))
{
$date = new Zend_Date($options['end_date']);
$date->setTime('23:59:59');
$date->setTimezone('UTC');
$options['end_date'] = $date->toString('yyyy-MM-dd HH:mm:ss');
}
date_default_timezone_set('UTC');
$options['mainTerms'] = array();
if(!empty($options['terms']) && !is_array($options['terms']))
{
$options['mainTerms'] = explode(' ', $options['terms']);
}
if(!is_array($options['terms']))
{
$options['terms'] = array();
}
if($options['group_id'] || $options['client_id'])
{
$TwitterSearches = new Model_DbTable_TwitterSearches();
$options['terms'] = array_merge($TwitterSearches->getList($options),$options['terms']);
if(empty($options['terms']))
{
$options['terms'] = array();
}
}
return $options;
}
public static function prepareSelect($select, $options)
{
if($options['start_date'])
{
$select->where('t.datetime >= ?', $options['start_date']);
}
if($options['end_date'])
{
$select->where('t.datetime <= ?', $options['end_date']);
}
foreach($options['mainTerms'] as $mainTerm)
{
$select->where('t.content LIKE ?', "%$mainTerm%");
}
if($options['user_id'])
{
$select->where("t.user_id = ?", $options['user_id']);
}
if($options['terms'])
{
$select->where('MATCH (t.content) AGASINT(?)', $options['terms']);
}
if($options['rate'])
{
if($options['rate'] == 'NotRated')
{
$select->where('r.rate IS NULL');
}
else
{
$select->where('r.rate = ?', $options['rate']);
}
}
if($options['last_update'])
{
$select->where('t.created_at > ?', $options['last_update']);
}
if($options['max_datetime'])
{
$select->where('t.created_at < ?', $options['max_datetime']);
}
return $select;
}
}
モデル:
<?php
class Model_TwitterPost extends Zf_Model
{
private $_name = 'twitter';
protected $_properties = array(
'id',
'status_id',
'user_id',
'content'
);
protected $_User = null;
public function setUser(Zf_Model $User)
{
$this->_User = $User;
}
public function getUser()
{
return $this->_User;
}
public function getPermalink()
{
return 'http://twitter.com/' . $this->screen_name . '/' . $this->status_id;
}
public function hasTerm($term)
{
if(preg_match("/\b$term\b/i", $this->getContent()))
{
return true;
}
return false;
}
public function getEntityName()
{
return $this->_name;
}
public function getUserProfileLink()
{
return $this->getUser()->getProfileLink() . '/status/' . $this->getStatusId();
}
}
抽象モデル(汎用オブジェクト):
<?php
abstract class Zf_Model
{
protected $_properties = array();
protected $_modified = array();
protected $_data = array();
protected $_new = true;
protected $_loaded = false;
public function __construct($id=false)
{
$id = (int)$id;
if(!empty($id))
{
$this->_data['id'] = (int)$id;
$this->setNew(false);
}
}
public function populate($data)
{
if(is_array($data) && count($data))
{
foreach($data as $k => $v)
{
if(in_array($k,$this->_properties))
{
$this->_data[$k] = $v;
}
}
}
$this->setLoaded(true);
}
public function setNew($new=true)
{
$this->_new = (bool)$new;
}
public function isNew()
{
return $this->_new;
}
public function setLoaded($loaded = true)
{
$this->_loaded = (bool)$loaded;
}
public function isLoaded()
{
return $this->_loaded;
}
public function __call($methodName, $args) {
if(method_exists($this, $methodName))
{
return $this->$methodName($args);
}
$property = $methodName;
if (preg_match('~^(set|get)(.*)$~', $methodName, $matches))
{
$filter = new Zend_Filter_Word_CamelCaseToUnderscore();
$property = strtolower($filter->filter($matches[2]));
if(in_array($property, $this->_properties))
{
if('set' == $matches[1])
{
$this->_data[$property] = $args[0];
if(true === $this->isLoaded())
{
$this->_modified[$property] = true;
}
return $this;
}
elseif('get' == $matches[1])
{
if(array_key_exists($property, $this->_data))
{
return $this->_data[$property];
}
throw new Exception("The property $property or $methodName() method was not setted for " . get_class($this));
}
}
}
throw new Exception("The property '$property' doesn't exists.");
}
public function __get($key)
{
if(isset($this->_data[$key]))
{
return $this->_data[$key];
}
return $this->$key;
}
public function __set($key,$value)
{
if(array_key_exists($key,$this->_properties))
{
$this->_data[$key] = $value;
return;
}
$this->$key = $value;
}
public function getId()
{
return (!$this->_data['id']) ? null : $this->_data['id'];
}
public function toArray()
{
// If it's a new object
if(true === $this->isNew())
{
return $this->_data;
}
// Else, if it's existing object
$data = array();
foreach($this->_modified as $k=>$v)
{
if($v)
{
$data[$k] = $this->_data[$k];
}
}
if(count($data))
{
return $data;
}
return false;
}
public function reload()
{
$this->_modified = array();
}
}
私のここでのロジックは、MVCパターンの無知に起因するわずかな欠陥であることがわかりました。この質問の私の提案された解決策は、実際にはマッパーごとに1つのテーブルしか存在しないので、良いものではありません。複数の同時挿入が必要な場合は、コントローラから呼び出すか、別のモデルを使用して呼び出す必要があります。 – Dan