I am new to Zend and have been attempting to follow the Zend Quick Start Guide's example of using Data Mappers and extending Zend_Db_Table_Abstract. I think I've grasped the general concepts, but I am now wondering, how I would go about modifying the guide's example code to allow for multiple tables.
Here is the part of the code I am currently interested in modifying:
protected $_dbTable;
public function setDbTable($dbTable)
{
if (is_string($dbTable)) {
$dbTable = new $dbTable();
}
if (!$dbTable instanceof Zend_Db_Table_Abstract) {
throw new Exception('Invalid table data gateway provided');
}
$this->_dbTable = $dbTable;
return $this;
}
public function getDbTable()
{
if (null === $this->_dbTable) {
$this->setDbTable('Application_Model_DbTable_Guestbook');
}
return $this->_dbTable;
}
I have changed it to this:
protected $_dbTables;
public function setDbTable($dbTable, $tableName)
{
if (is_string($dbTable)) {
$dbTable = new $dbTable();
}
if (!$dbTable instanceof Zend_Db_Table_Abstract) {
throw new Exception('Invalid table data gateway provided');
}
$this->_dbTables[$tableName] = $dbTable;
return $this;
}
public function getDbTables()
{
if (null === $this->_dbTables) {
$this->setDbTable('Application_Model_DbTable_Courses', 'courses');
$this->setDbTable('Application_Model_DbTable_CourseTimes', 'course_times');
}
return $this->_dbTables;
}
Is this a correct way to go about implementing multiple tables within the Data Mapper pattern or would you do it differently? Thanks for your help in advance!
Assuming that you want to return data from related tables, you should either add queries with joins to one Zend_Db_Table or use Zend_Db_Table_Relationships to fetch associated data. The benefit of using Joins is that you will only do one query over many when using Relationships. The drawback is that joined tables wont return Zend_Db_Table_Row objects (iirc), but since you are going to map them onto your Domain objects anyway, it's not that much of an issue.
Structurally, you can do like I suggested in How to change Zend_Db_Table name within a Model to insert in multiple tables. Whether you create a Gateway of Gateways or simply aggregate the Table Gateways in the DataMapper directly is really up to you. Just compose them as you see fit.
I'm not sure if it's the best practice, but, this is my abstract mapper:
<?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;
}
}
And this is one for my implemented mappers:
<?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;
}
}
The Model:
<?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();
}
}
Abstract model (Generic Object):
<?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();
}
}
Related
Is good technique to use Adaptive Model and Adaptive Repository in PHP?
Can somebody tell me what is wrong with the code, is good or is bad practice.
I use this function in Main Controller to load and configure Database, Model and Repository
public function fromDatabase($table, $rows = []) {
if(!isset($this->db_params[$table])) {
$this->db_params[$table] = Loader::DatabaseFastQueryDaemon();
}
$db_params = $this->db_params[$table];
return $db_params($table, $rows);
}
This is the code from Loader class
const DatabaseFastQueryDaemon = '../Core/Database/FastQuery/Daemon/DatabaseAdaptive.php';
public static function DatabaseFastQueryDaemon() {
return require(self::DatabaseFastQueryDaemon);
}
This is from Daemon/DatabaseAdaptive.php
return function($table, $rows) {
return \Core\Database\FastQuery\Manager\DatabaseAdaptive::managerData($table, $rows);
};
This is from Manager/DatabaseAdaptive
class DatabaseAdaptive {
public static function managerData($table, $rows) {
return new \Core\Database\FastQuery\Repository\DatabaseAdaptive(
new \Core\Database\FastQuery\Model\AdaptiveModel(
new \Core\Database\Database,
new \Core\Database\SqlQuery($table, $rows)
)
);
}
}
This is from Model/AdaptiveModel
class AdaptiveModel extends \Core\Database\FastQuery\Model\AdaptiveModelData {
public function __construct(\Core\Database\Database $myDatabase, \Core\Database\SqlQuery $sqlQuery) {
$this->myDatabase = $myDatabase;
$this->sqlQuery = $sqlQuery;
}
}
This is from Model/AdaptiveModelData
class AdaptiveModelData extends \Core\Database\FastQuery\Model\AdaptiveModelInit {
public function getDataBy($key, $var, $op = '=') {
try {
$sql = $this->sqlQuery;
$sql->select()->where([$key => [$op, $var]]);
return $this->myDatabase->query($sql->sql(), $this->fetchObjMode);
} catch (\PDOException $e) {
echo $e->getMessage();
}
}
}
This is from Model/AdaptiveModelInit
class AdaptiveModelInit {
protected $fetchObjMode = false;
public function setObjMode($mode) {
$this->fetchObjMode = $mode;
}
public function getData() {
try {
$sql = $this->sqlQuery;
$sql->select()->order(['id' => 'ASC']);
return $this->myDatabase->query($sql->sql(), $this->fetchObjMode);
} catch (\PDOException $e) {
echo $e->getMessage();
}
}
public function getLastData() {
try {
$sql = $this->sqlQuery;
$sql->select()->order(['id' => 'desc'])->limit([1 => 1]);
return $this->myDatabase->query($sql->sql(), $this->fetchObjMode);
} catch (\PDOException $e) {
echo $e->getMessage();
}
}
}
This is from Repository/Repository
class Repository {
protected $data = [];
public function setObjMode($mode) {
$this->model->setObjMode($mode);
}
public function countAll() {
return count($this->data);
}
public function countRow($row, $val) {
$count = 0;
foreach($this->data as $key => $val) {
foreach($val as $key2 => $val2) {
if($row == $key2 && $val == $val2) {
$count++;
}
$this->data[$key][$key2] = null;
unset($this->data[$key][$key2]);
}
$this->data[$key] = null;
unset($this->data[$key]);
}
return $count;
}
public function clearData() {
$this->data = null;
unset($this->data);
}
public function getCount() {
$this->data = $this->model->getCount();
}
public function getData() {
$this->data = $this->model->getData();
}
public function getLastData() {
$this->data = $this->model->getLastData();
}
public function getDataBy($key, $var, $op = '=') {
$this->data = $this->model->getDataBy($key, $var, $op);
}
}
This is from Repository/DatabaseAdaptive
class DatabaseAdaptive extends \Core\Database\FastQuery\Repository\RepositoryFindMethods {
public function __construct(\Core\Database\FastQuery\Model\AdaptiveModel $model) {
$this->model = $model;
}
}
This is from Repository/RepositoryFindMethods
class RepositoryFindMethods extends \Core\Database\FastQuery\Repository\Repository {
public function findAll() {
return $this->data;
}
public function findRowsLimit($to) {
$i = 1;
$find = [];
$data = array_reverse($this->data);
foreach($data as $key => $row) {
if($to >= $i) {
$find[$key] = $row;
}
$i++;
}
return $find;
}
public function findOneRandom() {
$all = count($this->data);
if($all == 1) {
return $this->data[0];
}
if($all > 1) {
$rand = rand(0, ($all - 1));
return $this->data[$rand];
}
}
public function findBy($k, $v) {
foreach($this->data as $i => $r) {
if(is_object($r)) {
if(isset($r->$k) && $r->$k == $v) {
return $r;
}
} else if(is_array($r)) {
if(isset($r[$k]) && $r[$k] == $v) {
return $r;
}
}
}
}
public function findAllBy($k, $v) {
$matches = [];
foreach($this->data as $i => $r) {
if(is_object($r)) {
if(isset($r->$k) && $r->$k == $v) {
$matches[] = $r;
}
} else if(is_array($r)) {
if(isset($r[$k]) && $r[$k] == $v) {
$matches[] = $r;
}
}
}
return $matches;
}
public function findById($id) {
foreach($this->data as $i => $r) {
if(is_object($r)) {
if($r->id == $id) {
return $r;
}
} else if(is_array($r)) {
if($r['id'] == $id) {
return $r;
}
}
}
}
public function getOne() {
if(isset($this->data[0])) {
return $this->data[0];
}
return $this->data;
}
public function findLast() {
return end($this->data);
}
}
This is from Controller/Cities and how i fetch data from the database
$dbCities = $this->fromDatabase('_cities', ['id', 'title']);
$dbCities->getDataBy('state_id', $stateId);
$cities = $dbCities->findAll();
Part of the code is cutted, because is long but thats the main idea. Thanks
I am using a database class picked up from a tutorial on codecourse.com (I am just starting the migration from procedural to pdo) and I am slowly extending it to fit my needs. However, the one thing I cannot manage is to return the lastInsertId() to be used globally.
Using the register example from that tutorial
$user = new User();
$salt = Hash::salt(32);
try {
$user->create(array(
'username' => Input::get('username'),
'password' => Hash::make(Input::get('password'), $salt),
'salt' => $salt,
'firstname' => Input::get('first_name'),
'lastname' => Input::get('last_name'),
'joined' => date('Y-m-d H:i:s'),
'group' => 1
));
} catch(Exception $e) {
die($e->getMessage());
}
It is at this point that I want to get the lastInsertId() - the one of the just registered user. I am not sure whether it comes out of the Database class via the insert function
require_once 'core/init.php';
class DB {
private static $_instance = null;
private $_pdo,
$_query,
$_error = false,
$_results,
$_count = 0;
private function __construct() {
try {
$this->_pdo = new PDO('mysql:host=' . Config::get('mysql/host') . ';dbname=' . Config::get('mysql/db'), Config::get('mysql/username'), Config::get('mysql/password') );
} catch(PDOException $e) {
die($e->getMessage());
}
}
public static function getInstance() {
if(!isset(self::$_instance)) {
self::$_instance = new DB();
}
return self::$_instance;
}
public function query ($sql, $params = array()) {
$this->error = false;
if ($this->_query = $this->_pdo->prepare($sql)) {
$x = 1;
if(count($params)) {
foreach($params as $param) {
$this->_query->bindValue($x, $param);
$x++;
}
}
if($this->_query->execute()) {
$this->_results = $this->_query->fetchAll(PDO::FETCH_OBJ);
$this->_count = $this->_query->rowCount();
} else {
$this->_error = true;
}
}
return $this;
}
public function action ($action, $table, $where = array()) {
if (count($where) === 3) {
$operators = array('=', '>', '<', '>=', '<=');
$field = $where[0];
$operator = $where[1];
$value = $where[2];
if (in_array($operator, $operators)) {
$sql = "{$action} FROM {$table} WHERE {$field} {$operator} ?";
if(!$this->query($sql, array($value))->error()) {
return $this;
}
}
}
return false;
}
public function get ($table, $where) {
return $this->action('SELECT *', $table, $where);
}
public function delete ($table, $where) {
return $this->action('DELETE', $table, $where);
}
public function insert ($table, $fields = array()) {
$keys = array_keys($fields);
$values = '';
$x = 1;
foreach($fields as $field) {
$values .= '?';
if ($x < count($fields)) {
$values .= ', ';
}
$x++;
}
$sql = "INSERT INTO {$table} (`" . implode('`,`', $keys) . "`) VALUES ({$values})";
if (!$this->query($sql,$fields)->error()) {
return true;
}
echo $sql;
}
return false;
}
public function update ($table, $id, $fields = array()) {
$set = '';
$x = 1;
foreach ($fields as $name => $value) {
$set .= "{$name} = ?";
if ($x < count($fields)) {
$set .= ', ';
}
$x++;
}
$sql = "UPDATE {$table} SET {$set} WHERE id = {$id}";
if (!$this->query($sql,$fields)->error()) {
return true;
}
}
return false;
}
public function first () {
return $this->results()[0];
}
public function results () {
return $this->_results;
}
public function count () {
return $this->_count;
}
public function error () {
return $this->_error;
}
}
Or the User class via the create function
class User {
private $_db,
$_data,
$_sessionName,
$_cookieName,
$_isLoggedIn;
public function __construct($user = null) {
$this->_db = DB::getInstance();
$this->_sessionName = Config::get('session/session_name');
$this->_cookieName = Config::get('remember/cookie_name');
if (!$user) {
if (Session::exists($this->_sessionName)) {
$user = Session::get($this->_sessionName);
if ($this->find($user)) {
$this->_isLoggedIn = true;
} else {
//Logged out
}
}
} else {
$this->find($user);
}
}
public function update($fields=array(), $id = null) {
if (!$id && $this->isLoggedIn ()) {
$id = $this->data()->id;
}
if (!$this->_db->update('users', $id, $fields)) {
throw new Exception('There was a problem updating the account!');
}
}
public function create($fields) {
if (!$this->_db->insert('users', $fields)) {
throw new Exception('There was a problem creating an account!');
}
}
public function find($user=null) {
if ($user) {
$field = (is_numeric($user)) ? 'id' : 'username';
$data = $this->_db->get('users', array($field, '=', $user));
if ($data->count()) {
$this->_data = $data->first();
return true;
}
}
return false;
}
public function login($username=null, $password=null, $remember = false) {
if(!$username && !$password && $this->exists()) {
Session::put($this->_sessionName, $this->data()->id );
} else {
$user = $this->find($username);
if ($this->data()->password === Hash::make($password, $this->data()->salt)) {
Session::put($this->_sessionName, $this->data()->id);
if ($remember) {
$hash = Hash::unique();
$hashCheck = $this->_db->get('users_session', array('user_id', '=', $this->data()->id));
if (!$hashCheck->count()) {
$this->_db->insert('users_session', array(
'user_id' => $this->data()->id,
'hash' => $hash
));
} else {
$hash = $hashCheck->first()->hash;
}
Cookie::put($this->_cookieName, $hash, Config::get('remember/cookie_expiry'));
}
return true;
}
return false;
}
}
public function hasPermission ($key) {
$group = $this->_db->get('groups', array('id', '=', $this->data()->group));
if($group->count()) {
$permissions = json_decode($group->first()->permissions, true);
if ($permissions[$key] == true) {
return true;
}
}
return false;
}
public function exists () {
return (!empty($this->_data)) ? true : false;
}
public function logout () {
Session::delete($this->_sessionName);
}
public function data () {
return $this->_data;
}
public function isLoggedIn () {
return $this->_isLoggedIn;
}
}
I have tried in both but whenever I try to echo the lastInsertId() back out, nothing is returned. Any advice would be greatly welcomed. If the problem might be outside of these areas, I have uploaded the entire script to https://github.com/MargateSteve/login.
Thanks in advance
Steve
The insert() method of a database class should return insert id. Here is a relevant part for it:
public function insert ($table, $fields = array()) {
$this->query($sql,$fields);
return $this->_db->lastInsertId;
}
while create() method of a User class should create a user instance.
public function create($fields) {
$id = $this->_db->insert('users', $fields);
$this->find($id);
}
Note that insert() method is vulnerable to SQL injection.
And now you can use your newly created user all right
$user = new User();
$user->create(array(
'username' => Input::get('username'),
'password' => Hash::make(Input::get('password')),
'firstname' => Input::get('first_name'),
'lastname' => Input::get('last_name'),
'joined' => date('Y-m-d H:i:s'),
'group' => 1
));
echo $user->data['id'];
I hope you are looking not for the code to copy and paste but for the understanding. And I hope you understand the logic above.
Add a public variable to your DB class that will hold the last inserted record ID:
class DB {
public $lastInsertId = null;
In the same DB class modify the query method, where the actual insert happens so that you can grab the ID from PDO:
public function query ($sql, $params = array()) {
$this->error = false;
if ($this->_query = $this->_pdo->prepare($sql)) {
$x = 1;
if(count($params)) {
foreach($params as $param) {
$this->_query->bindValue($x, $param);
$x++;
}
}
if($this->_query->execute()) {
$this->lastInsertId = $this->_pdo->lastInsertId();
$this->_results = $this->_query->fetchAll(PDO::FETCH_OBJ);
$this->_count = $this->_query->rowCount();
} else {
$this->_error = true;
}
}
return $this;
}
On the code above this is the important line:
$this->lastInsertId = $this->_pdo->lastInsertId();
You are assigning the value of PDO::lastInsertId() to your lastInsertId instance variable, that way you can access it from wherever you instantiate a DB object.
Now, modify the user class to hold a variable called id as well, do not name it lastInsertId because its confusing; in this context you have one single instance of a user which represents a single user and hence id simply refers to this instance user id:
class User {
public $id = null;
Modify in this same User class the create method as well to grab the lastInsertId value from your db object instance:
public function create($fields) {
if (!$this->_db->insert('users', $fields)) {
throw new Exception('There was a problem creating an account!');
}
$this->id = $this->_db->lastInsertId;
}
Then you can access the user ID in your register.php file simply accesing the user instance variable e.g. $user->id :
try {
$user->create(array(
'username' => Input::get('username'),
'password' => Hash::make(Input::get('password'), $salt),
'salt' => $salt,
'firstname' => Input::get('first_name'),
'lastname' => Input::get('last_name'),
'joined' => date('Y-m-d H:i:s'),
'group' => 1
));
Session::flash('home', "You have registered with user ID $user->id");
Redirect::to('index.php');
} catch(Exception $e) {
die($e->getMessage());
}
I tried to set up a sign up logic but suffer a problem said
Message: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'member_login' cannot be null, query was: INSERT INTO members (member_login) VALUES (?)enter image description here
After struggling for hours, still no ideas which go wrong. Here is my source code.
Anyone can give me some ideas?
My Model.php
<?php
class Application_Model_Member
{
protected $_id;
protected $_member_login;
public function __construct(array $options = null)
{
if (is_array($options)) {
$this->setOptions($options);
}
}
public function __set($name, $value)
{
$method = 'set' . $name;
if (('mapper' == $name) || !method_exists($this, $method)) {
throw new Exception('Invalid member property');
}
$this->$method($value);
}
public function __get($name)
{
$method = 'get' . $name;
if (('mapper' == $name) || !method_exists($this, $method)) {
throw new Exception('Invalid member property');
}
return $this->$method();
}
public function setOptions(array $options)
{
$methods = get_class_methods($this);
foreach ($options as $key => $value) {
$method = 'set' . ucfirst($key);
if (in_array($method, $methods)) {
$this->$method($value);
}
}
return $this;
}
public function setId($id)
{
$this->_id = (int) $id;
return $this;
}
public function getId()
{
return $this->_id;
}
public function setMemberLogin($text)
{
$this->_member_login = (string) $text;
return $this;
}
public function getMemberLogin()
{
return $this->_member_login;
}
}
My MemberMapper.php
<?php
class Application_Model_MemberMapper
{
protected $_dbTable;
public function setDbTable($dbTable)
{
if (is_string($dbTable)) {
$dbTable = new $dbTable();
}
if (!$dbTable instanceof Zend_Db_Table_Abstract) {
throw new Exception('Invalid table data gateway provided');
}
$this->_dbTable = $dbTable;
return $this;
}
public function getDbTable()
{
if (null === $this->_dbTable) {
$this->setDbTable('Application_Model_DbTable_Members');
}
return $this->_dbTable;
}
public function save(Application_Model_Member $member)
{
$data = array(
'member_login' => $member->getMemberLogin(),
);
if (null === ($id = $member->getId())) {
unset($data['member_id']);
$this->getDbTable()->insert($data);
} else {
$this->getDbTable()->update($data, array('member_id = ?' => $id));
}
}
public function find($id, Application_Model_Member $member)
{
$result = $this->getDbTable()->find($id);
if (0 == count($result)) {
return;
}
$row = $result->current();
$member->setId($row->member_id)
->setMemberLogin($row->member_login);
}
public function fetchAll()
{
$resultSet = $this->getDbTable()->fetchAll();
$entries = array();
foreach ($resultSet as $row) {
$entry = new Application_Model_Member();
$entry->setId($row->member_id)
->setMemberLogin($row->member_login);
$entries[] = $entry;
}
return $entries;
}
}
DbTable:
class Application_Model_DbTable_Members extends Zend_Db_Table_Abstract
{
protected $_name = 'members';
}
Form: Registration.php
<?php
class Application_Form_Auth_Registration extends Zend_Form
{
public function init()
{
$this->setMethod('post');
$this->addElement(
'text', 'member_login', array(
'label' => 'Username:',
'required' => true,
'filters' => array('StringTrim')
));
$this->addElement('submit', 'register', array(
'ignore' => true,
'label' => 'Sign up'
));
}
}
Signup controller:
public function signupAction()
{
$request = $this->getRequest();
$regform = new Application_Form_Auth_Registration();
if ($this->getRequest()->isPost()) {
if ($regform->isValid($request->getPost())) {
$member = new Application_Model_Member($regform->getValues());
$mapper = new Application_Model_MemberMapper();
$mapper->save($member);
return $this->_helper->redirector('/books/view');
}
}
$this->view->regform = $regform;
}
Finally I fix the bug. It go wrong with the naming of Element. For example, in your database you have "member_login", then the Element name should be sth like memberLogin.
I have currently two classes.
the ArrayCompare class:
<?php
namespace App\Tools\RegexExtract;
class ArrayCompare
{
public function compare(Array $arrayToCompare)
{
$elementData = new ElementMetaData();
$metaData = $elementData->extract($arrayToCompare[0], [], $initial=true);
foreach ($arrayToCompare as $currentElement) {
$metaData = $elementData->extract($currentElement, $metaData);
}
return $metaData;
}
}
which uses the ElementMetaData class
<?php
/**
* A class for extracting meta data from an element.
*/
namespace App\Tools\RegexExtract;
class ElementMetaData
{
public function extract($element, $metaDataToCompare = [], $initial = false)
{
if ($initial == true) {
$this->isInteger($element) ? $returnMetaData['isInteger'] = $this->isInteger($element) : null;
$returnMetaData['length'] = $this->length($element);
}
else {
$returnMetaData=$metaDataToCompare;
if ($returnMetaData != []) {
if (isset ($returnMetaData['isInteger']) && !$this->isInteger($element)) {
unset($returnMetaData['isInteger']);
}
if (isset ($returnMetaData['length']) && $this->length($element) != $returnMetaData['length']) {
unset($returnMetaData['length']);
}
}
}
return $returnMetaData;
}
private function isInteger($element)
{
return is_int($element);
}
private function length($element)
{
return strlen($element);
}
}
the basic functionality is:
given I have an array
$arr=[1,2,3];
I want to get the "similarities" between ALL Elements. According to a an array i Predefine...so this would deliver this result:
$metaArray=['isInteger'=>true,'length'=>1];
and this would deliver just length as similarity:
$arr=[1,2,'D'];
$metaArray=['length'=>1];
While this array would deliver an empty result []
$arr=[1,2,'3D']; // result is [] since not all integers or not all of same length.
Now my solution does not use recursive functions...but I am sure it can be used somehow.
Also, I want to add more "criteria"....So "isEmailAdress", "beginswithA"....etc....and this would make my if statements a horror....so what is the best strategy/design pattern to follow here?
#deceze beat me to it by fair margin... but I'll still post my solution that works basically with the same principles.
abstract class abstractComparer
{
private $array;
private $result = true;
protected $name;
public function compareArray($array)
{
$current = null;
foreach ($array as $index => $value)
{
$this->result = $this->result && $this->compareValues($index, $current, $value);
$current = $value;
}
}
public function getResult()
{
return $this->result;
}
public function getName()
{
return $this->name;
}
public abstract function compareValues($index, $value1, $value2);
public abstract function getSuccessValue();
}
class intComparer extends abstractComparer
{
protected $name = "isInteger";
public function compareValues($index, $value1, $value2)
{
return is_int($value2);
}
public function getSuccessValue()
{
return true;
}
}
class lengthComparer extends abstractComparer
{
protected $name = "length";
protected $length = 0;
public function compareValues($index, $value1, $value2)
{
$this->length = strlen($value2);
return $index == 0 || strlen($value1) == $this->length;
}
public function getSuccessValue()
{
return $this->length;
}
}
And do the actual processing like this:
$temp = [1,2,3];
$comparers = [new intComparer(), new lengthComparer()];
$result = array();
foreach ($comparers as $comparer)
{
$comparer->compareArray($temp);
if ($comparer->getResult())
{
$result[$comparer->getName()] = $comparer->getSuccessValue();
}
}
//var_dump($result);
I don't see any need for recursion here, so I'll just make a suggestion for a design approach:
Implement each criterion as a class:
abstract class Criterion {
protected $valid = true;
abstract public function initialize($value);
abstract public function check($value);
public function isValid() {
return $this->valid;
}
}
class Length extends Criterion {
protected $length;
public function initialize($value) {
$this->length = strlen($value);
}
public function check($value) {
if ($this->length != strlen($value)) {
$this->valid = false;
}
}
}
You then make an array of all your criteria:
$criteria = [new Length, ...];
foreach ($criteria as $criterion) {
$criterion->initialize($values[0]);
}
And slowly whittle them down through your values:
foreach ($values as $value) {
foreach ($criteria as $criterion) {
$criterion->check($value);
}
}
$commonCriteria = array_filter($criteria, function (Criterion $criterion) {
return $criterion->isValid();
});
I generated with Admin generator this:
abstract class autoNewsActions extends sfActions
{
public function preExecute()
{
$this->configuration = new newsGeneratorConfiguration();
if (!$this->getUser()->hasCredential($this->configuration->getCredentials($this->getActionName())))
{
$this->forward(sfConfig::get('sf_secure_module'), sfConfig::get('sf_secure_action'));
}
$this->dispatcher->notify(new sfEvent($this, 'admin.pre_execute', array('configuration' => $this->configuration)));
$this->helper = new newsGeneratorHelper();
parent::preExecute();
}
public function executeIndex(sfWebRequest $request)
{
// sorting
if ($request->getParameter('sort') && $this->isValidSortColumn($request->getParameter('sort')))
{
$this->setSort(array($request->getParameter('sort'), $request->getParameter('sort_type')));
}
// pager
if ($request->getParameter('page'))
{
$this->setPage($request->getParameter('page'));
}
$this->pager = $this->getPager();
$this->sort = $this->getSort();
}
public function executeFilter(sfWebRequest $request)
{
$this->setPage(1);
if ($request->hasParameter('_reset'))
{
$this->setFilters($this->configuration->getFilterDefaults());
$this->redirect('#news');
}
$this->filters = $this->configuration->getFilterForm($this->getFilters());
$this->filters->bind($request->getParameter($this->filters->getName()));
if ($this->filters->isValid())
{
$this->setFilters($this->filters->getValues());
$this->redirect('#news');
}
$this->pager = $this->getPager();
$this->sort = $this->getSort();
$this->setTemplate('index');
}
public function executeNew(sfWebRequest $request)
{
$this->form = $this->configuration->getForm();
$this->news = $this->form->getObject();
}
public function executeCreate(sfWebRequest $request)
{
$this->form = $this->configuration->getForm();
$this->news = $this->form->getObject();
$this->processForm($request, $this->form);
$this->setTemplate('new');
}
public function executeEdit(sfWebRequest $request)
{
$this->news = $this->getRoute()->getObject();
$this->form = $this->configuration->getForm($this->news);
}
public function executeUpdate(sfWebRequest $request)
{
$this->news = $this->getRoute()->getObject();
$this->form = $this->configuration->getForm($this->news);
$this->processForm($request, $this->form);
$this->setTemplate('edit');
}
public function executeDelete(sfWebRequest $request)
{
$request->checkCSRFProtection();
$this->dispatcher->notify(new sfEvent($this, 'admin.delete_object', array('object' => $this->getRoute()->getObject())));
if ($this->getRoute()->getObject()->delete())
{
$this->getUser()->setFlash('notice', 'The item was deleted successfully.');
}
$this->redirect('#news');
}
protected function processForm(sfWebRequest $request, sfForm $form)
{
$form->bind($request->getParameter($form->getName()), $request->getFiles($form->getName()));
if ($form->isValid())
{
$notice = $form->getObject()->isNew() ? 'The item was created successfully.' : 'The item was updated successfully.';
try {
$news = $form->save();
} catch (Doctrine_Validator_Exception $e) {
$errorStack = $form->getObject()->getErrorStack();
$message = get_class($form->getObject()) . ' has ' . count($errorStack) . " field" . (count($errorStack) > 1 ? 's' : null) . " with validation errors: ";
foreach ($errorStack as $field => $errors) {
$message .= "$field (" . implode(", ", $errors) . "), ";
}
$message = trim($message, ', ');
$this->getUser()->setFlash('error', $message);
return sfView::SUCCESS;
}
$this->dispatcher->notify(new sfEvent($this, 'admin.save_object', array('object' => $news)));
if ($request->hasParameter('_save_and_add'))
{
$this->getUser()->setFlash('notice', $notice.' You can add another one below.');
$this->redirect('#news_new');
}
else
{
$this->getUser()->setFlash('notice', $notice);
$this->redirect(array('sf_route' => 'news_edit', 'sf_subject' => $news));
}
}
else
{
$this->getUser()->setFlash('error', 'The item has not been saved due to some errors.', false);
}
}
protected function getFilters()
{
return $this->getUser()->getAttribute('news.filters', $this->configuration->getFilterDefaults(), 'admin_module');
}
protected function setFilters(array $filters)
{
return $this->getUser()->setAttribute('news.filters', $filters, 'admin_module');
}
protected function getPager()
{
$pager = $this->configuration->getPager('News');
$pager->setQuery($this->buildQuery());
$pager->setPage($this->getPage());
$pager->init();
return $pager;
}
protected function setPage($page)
{
$this->getUser()->setAttribute('news.page', $page, 'admin_module');
}
protected function getPage()
{
return $this->getUser()->getAttribute('news.page', 1, 'admin_module');
}
protected function buildQuery()
{
$tableMethod = $this->configuration->getTableMethod();
if (null === $this->filters)
{
$this->filters = $this->configuration->getFilterForm($this->getFilters());
}
$this->filters->setTableMethod($tableMethod);
$query = $this->filters->buildQuery($this->getFilters());
$this->addSortQuery($query);
$event = $this->dispatcher->filter(new sfEvent($this, 'admin.build_query'), $query);
$query = $event->getReturnValue();
return $query;
}
protected function addSortQuery($query)
{
if (array(null, null) == ($sort = $this->getSort()))
{
return;
}
if (!in_array(strtolower($sort[1]), array('asc', 'desc')))
{
$sort[1] = 'asc';
}
$query->addOrderBy($sort[0] . ' ' . $sort[1]);
}
protected function getSort()
{
if (null !== $sort = $this->getUser()->getAttribute('news.sort', null, 'admin_module'))
{
return $sort;
}
$this->setSort($this->configuration->getDefaultSort());
return $this->getUser()->getAttribute('news.sort', null, 'admin_module');
}
protected function setSort(array $sort)
{
if (null !== $sort[0] && null === $sort[1])
{
$sort[1] = 'asc';
}
$this->getUser()->setAttribute('news.sort', $sort, 'admin_module');
}
protected function isValidSortColumn($column)
{
return Doctrine_Core::getTable('News')->hasColumn($column);
}
}
how can i make add for this clause WHERE id > 10 for list in index? maybe in generator.yml?
Use the table_method generator.yml option.
In generator.yml:
config:
list:
table_method: buildQueryForAdminIndex
In NewsTable:
public function buildQueryForAdminIndex(Doctrine_Query $q)
{
$q->addWhere($q->getRootAlias() . '.id > ?', 10);
return $q;
}
I should note that hardcoding that id is poor form. It should be abstracted so that it is clear why the value is 10.