When I call a model from another model (using CodeIgniter &get_instance), there is an unwanted behavior, the constraints passed to the $this->db->where() and $this->db->select() previous model moves to the next model.
I'm working on scenario we need to call a model into another (chained / dependency) - though does not seem a common practice among the community CodeIgniter.
Below I have detailed my senary.
/Application/models/data_access/produto/produto_dao.php:
class Produto_dao extends CI_Model
{
private $ci;
public function __construct()
{
parent::__construct();
$this->ci =& get_instance();
$this->dependenciesEntity();
}
private function dependenciesEntity()
{
$this->ci->load->model('entity/produto/produto');
$this->ci->load->model('entity/produto/produto_collection');
$this->ci->load->model('data_access/produto/categoria_dao');
}
public function getProdutos($filtros, $inicioPaginacao = 0)
{
$this->db->select('produtos.id');
$this->db->select('produtos.uri');
$this->db->select('produtos.seo_keywords AS keywords');
$this->db->where('produtos.id', '1349');
$result = $this->db->get('produtos');
var_dump( $this->db->last_query() );
return $this->collectResult($result->result());
}
private function collectResult($result)
{
$produtoCollection = new Produto_collection();
foreach($result as $value){
$produto = new Produto();
$produto->setId($value->id);
$produto->setCategoria( $this->getCategoria( $value->id ) );
$produto->setUri($value->uri);
$produto->setKeywords($value->keywords);
$produtoCollection->addProduto($produto);
}
return $produtoCollection;
}
private function getCategoria($idProduto)
{
$categoria = new Categoria_dao();
return $categoria->getCategoria($idProduto);
}
}
And here we have the dependence,
/application/models/data_access/produto/categoria_dao.php
class Categoria_dao extends CI_Model
{
private $ci;
public function __construct()
{
parent::__construct();
$this->ci =& get_instance();
$this->dependenciesEntity();
}
private function dependenciesEntity()
{
$this->ci->load->model('entity/produto/categoria');
$this->ci->load->model('entity/produto/categoria_collection');
}
public function getCategoria($id)
{
$this->db->select('categorias.id');
$this->db->select('categorias.id_pai AS idPai');
$this->db->where('categorias.id',$id);
$result = $this->db->get('categorias');
var_dump( $this->db->last_query() );
return $this->collectResult($result->result());
}
private function collectResult($result)
{
$categorias = new Categoria_collection();
foreach($result as $value){
$categoria = new Categoria();
$categoria->setId($value->id);
$categoria->setCategoriaPai( $this->getCategoriaPai( $value->idPai ) );
$categorias->addCategoria($categoria);
}
return $categorias;
}
private function getCategoriaPai($id)
{
if($id){
return $this->getCategoria($id);
}
return new Categoria_collection();
}
}
When I run, I got the following results of $this->db->last_query()
Produto_dao.php last_query()
SELECT produtos.id, produtos.uri, produtos.seo_keywords AS keywords FROM produtos WHERE produtos.id = '3454'
Categoria_dao.php last_query()
SELECT produtos.id, produtos.uri, produtos.seo_keywords AS keywords, categorias.id, categorias.id_pai AS idPai FROM categorias WHERE categorias.id = '39' AND produtos.id = '3454'
Any idea why this happens?
You have to flush the cache to do this consecutive query as it is explain here :
CodeIgniter : Active recode caching
First query
$this->db->start_cache();
$this->db->select('field1');
$this->db->stop_cache();
When you are going to execute a new query, do :
$this->db->flush_cache();
It's going to give you a brand new query to work with.
Hope it helps.
Related
What I have is a product class, you can get a product via its id or its product nr. So I have created 2 constructors. The class is retrieving the product via the database and mapping the result to the class variables.
class Partnumber extends CI_Model
{
private $partNr;
private $description;
private $type;
public function __construct() {
}
public static function withId( $id ) {
$instance = new self();
$instance->loadByID( $id );
return $instance;
}
public static function withNr($partnumber) {
$instance = new self();
$instance->getIdFromPartnumber($partnumber);
return $instance;
}
protected function loadByID( $id ) {
$instance = new self();
$instance->getPartnumberFromId($id);
return $instance;
}
private function getIdFromPartnumber($partnumber){
$this->db->select("*");
$this->db->from('part_list');
$this->db->where('part_number', $partnumber);
$query = $this->db->get();
return $query->result_object();
}
//get the partnumber from an part id
private function getPartnumberFromId($partId){
$this->db->select("*");
$this->db->from('part_list');
$this->db->where('id', $partId);
$query = $this->db->get();
$this->mapToObject($query->result());
}
private function mapToObject($result){
$this->partNr = $result[0]->Part_number;
$this->description = $result[0]->Description;
$this->type = $result[0]->Type;
}
public function toJson(){
return json_encode($this->partNr);
}
}
The mapping works, (I know, I have to catch the errors). But all the values are null when I calling the toJson method.
I call it like this:
class TestController extends MX_Controller{
public function __construct(){
parent::__construct();
$this->load->model('Partnumber');
}
public function loadPage() {
$p = Partnumber::withId(1);
echo $p->toJson();
}
}
And yes, I know for sure that data is coming back, because I can print all the items in the mapping method. But why is the data gone when I acces it via toJson?
Your method withId calls loadByID which creates a new instance of your model. It does not load the data into the model that was created in withId which is returned
I'm building a small and simple PHP content management system and have chosen to adopt an MVC design pattern.
I'm struggling to grasp how my models should work in conjunction with the database.
I'd like to separate the database queries themselves, so that if we choose to change our database engine in the future, it is easy to do so.
As a basic concept, would the below proposed solution work, is there a better way of doing things what are the pitfalls with such an approach?
First, I'd have a database class to handle all MySQL specific pieces of code:
class Database
{
protected $table_name;
protected $primary_key;
private $db;
public function __construct()
{
$this->db = DatabaseFactory::getFactory()->getConnection();
}
public function query($sql)
{
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll();
}
public function loadSingle($id)
{
$sql = "SELECT * FROM $this->table_name WHERE $this->primary_key = $id";
return $this->query($sql);
}
public function loadAll()
{
$sql = "SELECT * FROM $this->table_name";
return $this->query($sql);
}
}
Second, I'd have a model, in this case to hold all my menu items:
class MenuItemModel
{
public $menu_name;
public $menu_url;
private $data;
public function __construct($data)
{
$this->data = $data;
$this->menu_name = $data['menu_name'];
$this->menu_url = $data['menu_url'];
}
}
Finally, I'd have a 'factory' to pull the two together:
class MenuItemModelFactory extends Database
{
public function __construct() {
$this->table_name = 'menus';
$this->primary_key = 'menu_id';
parent::__construct();
}
public function loadById($id)
{
$data = parent::loadSingle($this->table_name, $this->primary_key, $id);
return new MenuItemModel($data);
}
public function loadAll()
{
$list = array();
$data = parent::loadAll();
foreach ($data as $row) {
$list[] = new MenuItemModel($row);
}
return $list;
}
}
Your solution will work of course, but there are some flaws.
Class Database uses inside it's constructor class DatabaseFactory - it is not good. DatabaseFactory must create Database object by itself. However it okay here, because if we will look at class Database, we will see that is not a database, it is some kind of QueryObject pattern (see link for more details). So we can solve the problem here by just renaming class Database to a more suitable name.
Class MenuItemModelFactory is extending class Database - it is not good. Because we decided already, that Database is just a query object. So it must hold only methods for general querying database. And here you mixing knowledge of creating model with general database querying. Don't use inheritance. Just use instance of Database (query object) inside MenuItemModelFactory to query database. So now, you can change only instance of "Database", if you will decide to migrate to another database and will change SQL syntax. And class MenuItemModelFactory won't change because of migrating to a new relational database.
MenuItemModelFactory is not suitable naming, because factory purpose in DDD (domain-driven design) is to hide complexity of creating entities or aggregates, when they need many parameters or other objects. But here you are not hiding complexity of creating object. You don't even "creating" object, you are "loading" object from some collection.
So if we take into account all the shortcomings and correct them, we will come to this design:
class Query
{
protected $table_name;
protected $primary_key;
private $db;
public function __construct()
{
$this->db = DatabaseFactory::getFactory()->getConnection();
}
public function query($sql)
{
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll();
}
public function loadSingle($id)
{
$sql = "SELECT * FROM $this->table_name WHERE $this->primary_key = $id";
return $this->query($sql);
}
public function loadAll()
{
$sql = "SELECT * FROM $this->table_name";
return $this->query($sql);
}
}
class MenuItemModel
{
public $menu_name;
public $menu_url;
private $data;
public function __construct($data)
{
$this->data = $data;
$this->menu_name = $data['menu_name'];
$this->menu_url = $data['menu_url'];
}
}
class MenuItemModelDataMapper
{
public function __construct() {
$this->table_name = 'menus';
$this->primary_key = 'menu_id';
$this->query = new Query();
}
public function loadById($id)
{
$data = $this->query->loadSingle($this->table_name, $this->primary_key, $id);
return new MenuItemModel($data);
}
public function loadAll()
{
$list = array();
$data = $this->query->loadAll();
foreach ($data as $row) {
$list[] = new MenuItemModel($row);
}
return $list;
}
}
Also consider reading this:
DataMapper pattern
Repository pattern
DDD
I worked with procedural PHP for a long time but not to long ago I just started to learn OOP PHP. For better understanding I decided to create a class to manage my DB. As I started to learn from phpacademy my first select function was quite poor, so I just added some other arguments. I ended up with this:
public function get($tabel, $where = null, $columns = array('*'), $other = null){
if($where){ $where = $this->where($where);; }
$select = 'SELECT '.$this->select($columns);
return $this->action($select, $tabel, $where, $other);
}
// $db->get('users',array('group',1),array(*),array('LIMIT' => 10));
(action executes the query)
Then I decided to modify this to get better control.
public function getModified($table, $param = array()){
$select = (isset($param['S'])) ? $this->select($param['S']) : '*';
$where = (isset($param['W'])) ? $param['W'] : array();
$other = array();
if(isset($param['GB'])){ $other['GROUP BY'] = $param['GB']; }
if(isset($param['OB'])){ $other['ORDER BY'] = $param['OB']; }
if(isset($param['L'])){ $other['LIMIT'] = $param['L']; }
return $this->action('SELECT '.$select, $table, $where, $other);
}
// $db->getModified('users',array('WHERE' => array('id',1), 'LIMIT' => 10));
But today I found in FuelPHP's documentation this: DB::get()->from('users')->where('id', 1)->limit(10);
Because I do this class to practice OOP PHP I've tried to create something similar but to execute the query I had to add an other function, which I want to skip. Could you show me an example how this method should/could work?
And I know that it's objective but which one would you prefer?
I'll just explain how the FuelPHP way works. This is btw a pattern that is used in a lot of DB wrapper classes.
In short, your Database package consists of 2 classes. 1 that handles connection to your database, multiple connections,... This is the DB class. this will look something like this:
class DB
{
private static $connections = array();
public static function addConnection($name, $connection)
{
self::$connection[$name] = $connection;
}
public static function get($name='default')
{
return new QueryBuilder(self::$connection[$name]);
}
}
This class manages all connections and returns a queryBuilder instance if you need to query a connection. The QueryBuilder will look something like this:
class QueryBuilder
{
private $connection;
public function __construct($connection)
{
$this->connection = $connection;
}
public function select()
{
$this->queryType = 'SELECT';
return $this;
}
public function from($table)
{
$this->table = $table;
return $this;
}
public function all()
{
return $this->connection->query(
$this->getSqlQuery()
);
}
public function getSqlQuery()
{
return $this->queryType . ' ' . $this->columns . ' FROM ' . $this->table;
}
}
so now you can use the clases above as:
DB::setConnection('default', $myPdoConnection);
DB::get()->select()->from('my_table')->all();
Note that the Querybuilder assumes that your $connection has a query method
A nice example from the Eloquent/laravel QueryBuilder: https://github.com/illuminate/database/blob/master/Query/Builder.php
What you are searching for is method chaining, also fuelphp (just like doctrine and others) is caching what you are sending in and builds the query after that:
public function from($table){
$this->_table = $table;
return $this; //this is the important part for chaining
}
public function where($key,$value){
$this->where = array($key => $value);
return $this;
}
public function andWhere($key,$value){
if(!$this->where) $this->where = array();
$this->where[$key] = $value;
return $this;
}
public function getQuery(){
//Build query here from what we stored before
$query = '';
....
return $query;
}
Oh well, what i forgot is what DB::get returns, an instance of what the class above is and executes that then:
public static function get(){
return new Query(); //above functions are part of Query class
}
So I was trying to make a CRUD for my app. I have setup a controller, like this:
class Clients extends CI_Controller {
public function __construct() {
parent::__construct();
session_start();
$this->c = new Client();
$this->u = new User();
}
function index() {
$data['current_view'] = 'client_view';
$data['header'] = 'Меню клиентов';
$data['clients'] = $this->getClients();
$this->load->view('main_view',$data);
}
function getClients() {
$this->u->where('username',$_SESSION['username'])->get();
$c = $this->c->where('user_id',$this->u->id)->get();
return $c;
}
function delClient() {
$this->c->where('client_id', $this->uri->segment(3))->delete();
$this->c->skip_validation()->save();
$this->index();
}
}
However, when I'm trying to perform a delete on a client, i get a db error:
You must use the "set" method to update an entry.
Filename: C:\OpenServer\domains\localhost\system\database\DB_active_rec.php
Line Number: 1174
What might be the cause of this? I found a similar question here, but I don't think that's the case.
EDIT: Client model:
class Client extends DataMapper {
public $has_one = array('user');
public function __construct($id = NULL) {
parent::__construct($id);
}
}
you have not passed the uri segment to the function delClient() and this is your model right...
function delClient($id) {
//$id is the value which you want to delete
$this->c->where('client_id', $id)->delete();
$this->c->skip_validation()->save();
$this->index();
}
I have something like this:
class MyParent {
protected static $object;
protected static $db_fields;
public function delete() {
// delete stuff
}
public static function find_by_id($id = 0) {
global $database;
$result_array = self::find_by_sql("SELECT * FROM " . static::$table_name . " WHERE id=" . $database -> escape_value($id) . " LIMIT 1");
return !empty($result_array) ? array_shift($result_array) : false;
}
public static function find_by_sql($sql = "") {
global $database;
// Do Query
$result_set = $database -> query($sql);
// Get Results
$object_array = array();
while ($row = $database -> fetch_array($result_set)) {
$object_array[] = self::instantiate($row);
}
return $object_array;
}
private static function instantiate($record) {
$object = self::$object;
foreach ($record as $attribute => $value) {
if (self::has_attribute($attribute)) {
$object -> $attribute = $value;
}
}
return $object;
}
}
class TheChild extends MyParent {
protected static $db_fields = array('id', 'name');
protected static $table_name = "my_table";
function __construct() {
self::$object = new TheChild;
}
}
$child= TheChild::find_by_id($_GET['id']);
$child->delete();
I get this: Call to undefined method stdClass::delete() referring to the last line above. What step am I missing for proper inheritance?
You never actually instanciate the TheChild class, which should be done by
$var = new TheChild();
except in TheChild constructor itself.
So, the static $object field is never affected (at least in your example), so affecting a field to it (the line $object -> $attribute = $value; ) causes the creation of an stdClass object, as demonstrated in this interactive PHP shell session:
php > class Z { public static $object; }
php > Z::$object->toto = 5;
PHP Warning: Creating default object from empty value in php shell code on line 1
php > var_dump(Z::$object);
object(stdClass)#1 (1) {
["toto"]=>
int(5)
}
This object does not have a delete method.
And as said before, actually creating a TheChild instance will result in an infinite recursion.
What you want to do is this, probably:
class TheChild extends MyParent {
protected static $db_fields = array('id', 'name');
protected static $table_name = "my_table";
function __construct() {
self::$object = $this;
}
}
Edit: Your updated code shows a COMPLETE different Example:
class MyParent {
protected static $object;
public function delete() {
// delete stuff
}
}
class TheChild extends MyParent {
function __construct() {
self::$object = new TheChild;
}
}
$child = new TheChild;
$child->delete();
Calling "Child's" Constructor from within "Child's" Constructor will result in an infinite loop:
function __construct() {
self::$object = new TheChild; // will trigger __construct on the child, which in turn will create a new child, and so on.
}
Maybe - i dont know what you try to achieve - you are looking for:
function __construct() {
self::$object = new MyParent;
}
ALSO note, that the :: Notation is not just a different Version for -> - it is completely different. One is a Static access, the other is a access on an actual object instance!