I thought about using an actual ORM like Doctrine, then I figured its download link was even broken...and all tutorials online dated back to 2011. Also I'll have to write yaml files.
Then I start off by trying to write my own model class in ORM style.
I just fill it up with fields and save it to database, which is easy.
But I encounter a problem trying to retrieve data from database.
class User extends CI_Model {
public $id;
public $email;
public $displayName;
public function save() {
.....
}
public function search_by_email($email) {
$user = new User();
$this->db->select('email')->from('user')->where('email', $email);
$result = $this->db->get();
if ($result->num_rows()==0) {
return false;
}else {
foreach ($result->result() as $field) {
}
}
}
I know normally in CodeIgniter, you return $query->result(), well as ORM custom, I'm trying to return an object...Is there a way to do this? What function should I use?
result takes a string that represents a class that it will instantiate and assigned the result data (each field as a property of the object):
$query = $this->db->query("SELECT * FROM users;");
foreach ($query->result('User') as $row)
{
echo $row->name; // call attributes
echo $row->reverse_name(); // or methods defined on the 'User' class
}
regarding your comment, i'm pretty sure that codeigniter has no idea about anything regarding the class you pass to the result method. It looks like it just instantiates it and sets property and valuefor each column/value returned from the db:
$this->custom_result_object[$class_name][$i] = new $class_name();
foreach ($this->{$_data}[$i] as $key => $value)
{
$this->custom_result_object[$class_name][$i]->$key = $value;
}
One ORM that works quite well with codeIgniter is php-activerecord which is based off the rails active record model.
The function your trying to copy "search_by_email" is done through a
Late static binding method.
So you might see functions that are called like so:
Object::find_by_email()
Object::search_by_email()
Related
I am novice in OOP programming in php and trying to understand and implement the dependency injection feature in my MVC project. In the following I am explaining a super simple example of the feature where I am struggling applying the dependency injection. The actual application is lot more complex than this, however, this should be enough to demonstrate the problem I am having.
I have created a Model called “user” that is responsible for managing a single user. Data handled by the class (data about a user) is also saved in the database table. The “user” class has method to load from and save/update the data to the database table. The user class can be initiated with data loaded from the database (by using user id) or load from the array supplied to the constructor.
The project deals with multiple users at a time. So, I have created a container class called “users”. This class has an array of “user” objects. However, this class also have method to load data for multiple user objects from the database (based on criteria such as all paid users), then create the object array with the data. The number of object is created is depends on the number of users returned from the database.
The following is a sample code for the classes
class user
{
private $data;
function __construct ($arData=””)
{
$this->dbTable ="user";
if(!is_array($ar))
{
if($ar!="")
{
$ar = $this->getDataFromDB($ar);
}
else
{
$ar = array();
}
}
$this->data = $ar;
}
function getDataFromDB($id_user){ … data base implementation … }
....
Other methods
....
}
class users // the container class
{
private $objUsers;
function __ construct(){
$this->objUsers = array();
}
function loadUsers($type){
$userDataArray = $this->getUsersFromDatabase($type);
foreach($useDataArray as $userData){
$this->objUsers[] = new user($userData);
}
}
function getUsersFromDatabase($userType) { …… database …… }
…… other methods …..
}
My concern is the container class (container may not be the right word to say). I want to know the best practice to create this type of container class and what is the recommend for this. In addition, this is clearly evident that this container class is tightly coupled with “user” class and cannot be tested separately. How can I implement the dependency injection for a class like this?
As I said, I don't think this is a good fit for dependency injection. And I wouldn't set it up that way just for the sake of saying it uses dependency injection.
The main reason it's not a good fit, is that a User is always a User. So you always have a concrete contract between the wrapper, Users, and the User. You can count on User having certain methods. And you don't have some weird 3rd class that your adding into these collections, it's just a collection of a known and well defined object.
That said, I would go with a more factory style wrapper, Where the User is the simpler of the 2 classes. ( note, I didn't test any of this, so just look at it like psudo code )
class users {
public function createUser( array $data = []){
if( $data['id'] ){
$User = $this->getUser( $data['id'] );
if( $User )
return $User;
}
//depending what you want you could search on other things
//like the email, (unique) and return the user.
//you could make this as complex, or as simple as you want
//it could simply create a new user, or check for an existing one first.
return new User($data); //dependency
}
public function getUser( $id ){
$stmt = $this->DB->prepare( "SELECT * FROM users WHERE id=:id" );
$stmt->FetchMode(PDO::FETCH_CLASS, 'User');
return $stmt->fetch(); //fetch right into the class
}
public function saveUser( User $User ){
//I just added this to show how you can type hint the class
// so that it only accepts Objects of the type User
}
}
class user{
protected $id;
protected $username;
protected $email;
public function __construct(array $data = []){
foreach( $data as $field=>$value){
//this could be done better, with error checking etc.
//but I just wanted to show how you can use the setters
//dynamically when constructing with an array
//this is useful because we are not hard coding the schema of User in any files
//-note- PHP method calls are case insensitive.
$this->set{$field} = $value;
}
}
public function setId( $id ){ $this->id = $id; }
public function getId(){ return $this->id; }
public function setUsername( $username ){ $this->username = $username; }
public function getUsername(){ $this->username; }
public function setEmail( $email ){ $this->email = $email; }
public function getEmail(){ return $this->email; }
}
Then you can worry about dependency injection for things like the Database. This could be represented by having the users constructor accept a PDO or Database object. Like this
class Users{
protected $DB;
public function __construct( $DB ){
$this->DB = $DB;
}
}
The Users class doesn't care about the DB credentials, or even the particular DB driver your using. To some extent it does have some coupling with the driver based on the SQL syntax, which may be specific to a particular database. If we wanted to make this a "truer" form of dependency injection we should use an ORM like Doctrine, or some kind of Query builder ( instead of PDO itself ). Then we would have another layer of abstraction between our code and the database.
If you need user to have access to users and they cant be separated extend the class.
class users {
}
class user extends users {
}
Child user can then access the parent users properties.
I have the following classes:
namespace database;
class database {
protected $table = '';
function __construct()
{ // Stuff here }
public function all() {
// Load from database
// Return array with results
}
}
class tbl_organisation_types extends database {
protected $table = 'tbl_organisation_types';
function __construct($data = [])
{
parent::__construct();
// do stuff with $data
}
}
I fetch all rows from the database, iterate through them and create new objects. Since other classes extend this database class, the generated objects have to be dynamic.
When i try to generate the objects dynamically like this
$return[] = new $this->table($row);
I get the following error
Uncaught Error: Class 'tbl_organisation_types' not found in /path/to/tbl_organisation_types.php
If I generate the objects with a fixed classname like this it works. But then it wouldn't work for multiple classes.
$return[] = new tbl_organisation_types($row);
Is there a way to dynamically generate objects and/or why is mine not working?
Using PHP 7.1
I am getting an impression, that you are attempting to create something like active record there. I would advise against that.
As for why your class is not working, you probably should do something like:
$name = '\\database\\' . $this->table;
$instance = new $name($row);
When you use a string to initialize class, it has to contain the fully qualified class name, no matter in which namespace your code is. And same applies even to aliases.
I've created this class that fetches all data of a post from database.
class Post {
private $id;
protected $conn;
public $data;
function __construct(\mysqli $conn) {
$this->conn = $conn;
}
public function getId() {
return $this->id;
}
public function getConnection() {
return $this->conn;
}
public function getPost() {
$query1 = $this->getConnection()->query("SELECT * FROM posts WHERE id=" . $this->id);
if ($query1->num_rows == 1) {
$this->data = $query1->fetch_object();
return $this->data;
}
}
public function setId($id) {
$this->id = (int)$id;
}
}
Along with post, I also need to fetch all data of the user who created the post. I've three ways to do this:
1) By calling the User class inside of the Post class.
public function getPost() {
$query1 = $this->getConnection()->query("SELECT * FROM posts WHERE id=" . $this->id);
if ($query1->num_rows == 1) {
$this->data = $query1->fetch_object();
// Initiating User class
$user = new User($this->getConnection());
$user->setUserId($this->data->user_id);
$this->data->user = $user->getUserInfo();
return $this->data;
}
}
2) By extending the Post class with the User class.
class Post extends User {
....
Then calling methods from the User class
public function getPost() {
$query1 = $this->getConnection()->query("SELECT * FROM posts WHERE id=" . $this->id);
if ($query1->num_rows == 1) {
$this->data = $query1->fetch_object();
// Calling methods from the User class
$this->setUserId($this->data->user_id);
$this->data->user = $this->getUserInfo();
return $this->data;
}
}
3) By creating a User trait and using it in Post class.
class Post {
use UserTrait;
....
Then calling methods from the User trait
public function getPost() {
$query1 = $this->getConnection()->query("SELECT * FROM posts WHERE id=" . $this->id);
if ($query1->num_rows == 1) {
$this->data = $query1->fetch_object();
// Calling methods from the User trait
$this->setUserId($this->data->user_id);
$this->data->user = $this->getUserInfo();
return $this->data;
}
}
Between these 3, which one is the best approach in terms of dependency injection, performance, and cleanliness of code?
This question might get downvoted to hell because you're requesting an opinion, but I'll throw in my two cents.
You're basically building your own basic ORM. In that ORM you're dealing with entities. Different entities should be handled by different classes (Models), and there should be a 1:1 equivalence between the underlying DB table and the Model.
There are relations between your entities. Yous Post entity has an author that should be an instance of the User entity. However, you wouldn't say that the relation between the post table and the user table means they could be represented by the same Model, or different flavors of a parent Model.
If you used a full ORM (Doctrine, Propel, RedbeanPHP) you'd see that a relation such as the Post author means that, when using methods like getById() (in your case getPost() ) the retrieved Post entity will proceed to instance every depending entity by default.
In your case, instead of having $this->data->user_id the ORM would offer you a nested $this->data->user object so you wouldn't have to deal with that by yourself.
So, if you consider it's too soon to use an ORM, the first approach is easier to mantain and migrate when the time comes.
Unrelated opinion
At this point, to get a post info you need to do
$post = new Post($conn);
$post->setId($postid);
$postdata = $post->getPost();
If you changed your constructor and added a getById() method such that
function __construct(\mysqli $conn) {
$this->conn = $conn;
return $this;
}
public function getById($id) {
$this->setId($id);
return $this->getPost();
}
you could instead retrieve the post data in one line
$postdata=(new Post($conn))->getById($postid);
It all depends on what you're going to keep doing it. With the extended class was better deal if you're going to use permanent methods of other classes. If you need only a part of another class, then just call the class and its method.
CAREFUL: An extended class behaves as a single class. Here can be a lot of collisions occur if you have the same names of methods and functions.
I think this depends on other aspects of your code. Personally I would just extend the class User as I don't think using traits is necessary in this particular case.
There is a useful article here on using Traits in PHP -
Using Traits in PHP 5.4
I have Yii application and two tables with same structure tbl and tbl_history:
Now want to create model so it will select table by parameter I send when calling model. For example:
MyModel::model('tbl')->find();
//and
MyModel::model('tbl_history')->find();
Find related article with solution in Yii forum. Made same changes and finally got this in MyModel:
private $tableName = 'tbl'; // <=default value
private static $_models=array();
private $_md;
public static function model($tableName = false, $className=__CLASS__)
{
if($tableName === null) $className=null; // this string will save internal CActiveRecord functionality
if(!$tableName)
return parent::model($className);
if(isset(self::$_models[$tableName.$className]))
return self::$_models[$tableName.$className];
else
{
$model=self::$_models[$tableName.$className]=new $className(null);
$model->tableName = $tableName;
$model->_md=new CActiveRecordMetaData($model);
$model->attachBehaviors($model->behaviors());
return $model;
}
}
Now when I make:
echo MyModel::model('tbl_history')->tableName(); // Output: tbl_history
It returns right value, but:
MyModel::model('tbl_history')->find();
still returns value for tbl.
Added:
public function __construct($id=null,$scenario=null){
var_dump($id);
echo '<br/>';
parent::__construct($scenario);
}
and got:
string(tbl_history)
string(tbl_history)
NULL
It means Yii makes call to model from other place but don't know from where and how to prevent it.
Also It makes 2 calls to model, is it too bad for performance?
It looks like the CActiveRecord::getMetaData() method needs to be overridden to achieve what you are looking for.
<?php
class TestActiveRecord extends CActiveRecord
{
private $tableName = 'tbl'; // <=default value
private static $_models=array();
private $_md;
public function __construct($scenario='insert', $tableName = null)
{
if($this->tableName === 'tbl' && $tableName !== null)
$this->tableName = $tableName;
parent::__construct($scenario);
}
public static function model($tableName = false, $className=__CLASS__)
{
if($tableName === null) $className=null; // this string will save internal CActiveRecord functionality
if(!$tableName)
return parent::model($className);
if(isset(self::$_models[$tableName.$className]))
return self::$_models[$tableName.$className];
else
{
$model=self::$_models[$tableName.$className]=new $className(null);
$model->tableName = $tableName;
$model->_md=new CActiveRecordMetaData($model);
$model->attachBehaviors($model->behaviors());
return $model;
}
}
public function tableName()
{
return $this->tableName;
}
/**
* Returns the meta-data for this AR
* #return CActiveRecordMetaData the meta for this AR class.
*/
public function getMetaData()
{
if($this->_md!==null)
return $this->_md;
else
return $this->_md=static::model($this->tableName())->_md;
}
public function refreshMetaData()
{
$finder=static::model($this->tableName());
$finder->_md=new CActiveRecordMetaData($finder);
if($this!==$finder)
$this->_md=$finder->_md;
}
}
Maybe it's easier to make MyModelHistory which extends MyModel and overrides only one method - tableName().
I recommend implementing single table inheritance. In order to do this you will need to combine your tables with a flag or type column that states whether or not this is a history record. I've pasted a few links at the bottom so you can see how this is implemented in Yii and listed some of the benefits below.
Benefits:
You won't need to duplicate code commonly used between the models
Changes to this table will only need to be executed once.
Changes to the parent model will only need to be made once.
Code becomes generally more maintainable and readable.
You seperate the code that belongs specifically to tbl and tbl_history
http://www.yiiframework.com/wiki/198/single-table-inheritance/
http://en.wikipedia.org/wiki/Single_Table_Inheritance
I created a solution for performing this exact action a couple of months ago. This is a completely dynamic solution, you just pass the table name like you are looking for to the model. That solution was originally designed to work with the same database structure across multiple databases, but it was trivial to adapt it to work in the same database. The documentation for that is here. I'd recommend reading over it as it has more details about CDynamicRecord
It's easy to adapt to work with multiple tables. You can download the adaptation as a gist from github.
Usage
1) Download the Gist and drop it into ext, save as CDynamicRecordSDB.php
2) Create Your model in Model, and setup up as follows:
Basically, you want to extend CDynamicRecord, and override your model() and tableName() so they are compliant with CDyanmicRecord.
<?php
Yii::import('ext.CDynamicRecordSDB');
class Test extends CDynamicRecordSDB
{
public static function model($dbConnectionString = 0, $className=__CLASS__)
{
return parent::model($dbConnectionString, $className);
}
public function tableName()
{
return $this->dbConnectionString;
}
[... Do everything else after this ...]
}
3) Setup your model as you normally would.
Usage
The usage is identical to CActiveRecord, and you can perform all actions. No surprises. Just a couple examples below.
$data = Test::model('tbl')->findAll();
$data2 = new Test('tbl');
$data2->findAll();
foreach ($data as $row)
print_r($row->attributes);
$data = Test::model('tbl_history')->findAll();
foreach ($data as $row)
print_r($row->attributes);
Limitations
The only limitation with doing this is you have to modify how relations work. IF you plan on accessing a related model (Bar), and you have no intention on calling Bar by itself. Then Bar should extend CActiveRecord, and in Foo you can define normal relations. Yii magically carries over the CDbConnectionString across the instances for you.
OTHERWISE, if you intend to access models in the same database, but also want to retain the ability to call them by themselves, then Bar should extend CDynamicModel, and Foo should have a getter defined as follows.
public function getBar()
{
return Bar::model($this->$dbConnectionString);
}
A small way but work for me for any number of table
public static $dynamic_table_name="main_table";
public static function setDynamicTable($param)
{
self::$dynamic_table_name=self::$dynamic_table_name.$param;
}
/**
* #return string the associated database table name
*/
public function tableName($param='')
{
self::setDynamicTable($param);
return self::$dynamic_table_name;
}
// to use it like
ModelName::model()->tableName('_one');
ModelName::model()->tableName('_two');
ModelName::model()->tableName('_three');
I've been using Codeigniter to construct the front end of a moderately sized application, and have run into an issue with--what I think may be--inheritance in PHP. Here is what I am trying to do:
In following the MVC architecture, I found that I was duplicating a lot of code across models, and decided to create a common Model from which other models could inherit. Simple enough. However, now, I am getting issues with some of the functions which are defined in the common Model class.
Here is a sketch of what I'm doing:
<?php
/**
* Common Model
*
*/
class DeviceModel extends Model {
function DeviceModel() {
parent::Model();
}
public function getDeviceId($d) { // this is just example code. }
public function getDeviceInfo($id) {
$selectStmt = "SELECT BLAH, BLAH2 FROM YADDAYADDA...";
$query = $this->db->query($selectStmt, array($id));
if ($query->num_rows() > 0) {
return $query->result();
}
}
}
?>
Here is the subclass:
<?php
require_once('devicemodel.php');
class ManageModel extends DeviceModel {
function ManageModel() {
parent::DeviceModel();
}
function getDropDownList($parkId,$tableName,$userclass) {
$arrCmds = array();
$arrHtml = array();
$deviceInfo = parent::getDeviceInfo($parkId);
$did = parent::getDeviceId($deviceInfo);
foreach ($deviceInfo as $device) {
$cmds = $this->getDeviceCommands($device->dtype,$tableName,$userclass);
array_push($arrCmds,$cmds);
}
//
// **After the refactor, I am receiving Undefined Offsets for this loop.**
//
for ($i=0; $i<sizeof($arrCmds); $i++) {
$html = $this->generateHtml($arrCmds[$i],$did[$i]);
array_push($arrHtml,$html);
}
return $arrHtml;
}
Is there a problem using multiple inheritance in codeigniter? I am fairly new to PHP and codeigniter.
Thanks for looking.
I don't see where is the multiple inheritance there.
I'm also working with codeigniter, and I had the need to subclass it's Controller so all my Controllers can descend from mine and not from CodeIgniter's directly.
CodeIgniter has native methods for extending it's classes with your own. Or you could open the model.php file (in system/libraries/) and at the top of the file, right after the if (!defined ...), you could add the code of your ManageModel class
Also here's a link for extending the model http://www.askaboutphp.com/50/codeigniter-extending-the-native-model-and-make-it-your-own.html