CRUD stands for Create, Read, Update and Delete, the basic operations performed on the database. With regular CodeIgniter setups, most developers would use the Active Record Class for performing such operations. But we are not going to be doing that.
In this episode of the series, I will show you how to perform CRUD with Doctrine.
"CodeIgniter and Doctrine from Scratch" Series:
In Doctrine, there can be multiple ways of accomplishing the same task. This gives you flexibility to pick the best approach suitable for each situation. Therefore we shall explore these different methods for each part of the CRUD.
1. CREATE
The act of inserting new data/records into the database:
Record Object
This is the way we have been creating our user records in the previous tutorials. It’s pretty simple:
$u = new User(); $u->username = 'myuser'; $u->password = 'mypass'; $u->save();
When the save() method is called, the new record is created.
fromArray()
Sometimes you have all the data already in an array. Instead of assigning every value to the record object separately, you can simply use the fromArray() method.
$data = array( 'username' => 'myuser', 'password' => 'mypass', 'email' => 'my@email.com' ); $u = new User(); $u->fromArray($data); $u->save();
flush()
When you call the flush() method on the Doctrine_Connection object, all unsaved record objects automatically get saved. And Doctrine performs this in a single transaction.
$u = new User(); $u->username = 'myuser'; $u->password = 'mypass'; $u2 = new User(); $u2->username = 'foouser'; $u2->password = 'foopass'; $conn = Doctrine_Manager::connection(); $conn->flush();
This way you don’t need to call the save() method for each object.
Raw SQL
We can just give a plain SQL query to the Doctrine_Connection object and call the execute() method.
This also supports binding of values to the query (with the usage of ‘?’), which automatically filters against SQL injection.
$data = array('myuser','mypass');
$conn = Doctrine_Manager::connection();
$conn->execute('INSERT INTO user (username, password) VALUES (?,?)', $data);
2. READ
The act of fetching records from the database.
Most of these are performed on Doctrine_Table objects, which is obtained by calling Doctrine::getTable() and passing the model class name to it.
find()
The find() method fetches a record by the Primary Key value.
$user_id = 1;
$u = Doctrine::getTable('User')->find($user_id);
echo $u->username;
findOneBy*()
This is a magic method. You can simply append the column name, and it will search the table by that column.
For example, to find a user record by username:
$username = 'myuser';
$u = Doctrine::getTable('User')->findOneByUsername($username);
echo $u->username;
It fetches only one record.
findBy*()
Another magic method, which works similarly, but fetches multiple rows. It returns a Doctrine_Collection object.
You can treat this returned object just like an array, to get to the individual record.
(let’s assume we have a column named ’status’):
$users = Doctrine::getTable('User')->findByStatus('active');
echo $users[0]->username;
echo $users[1]->username;
Even if the actual column name is all lowercase, it can be used as capitalized in that magic function call (status vs. Status).
DQL
This is our first look at DQL in these tutorials. It stands for Doctrine Query Language. It is actually a major feature. Most advanced queries are performed using DQL.
In this example, we first create the query object, and add the components of the query to it. All the method calls can be chained nicely. Finally we execute the query, which returns us a Doctrine_Collection object, like that last example.
$status = 'active';
$q = Doctrine_Query::create()
->select('username')
->from('User')
->where('status = ?', $status)
->limit(20);
$users = $q->execute();
echo $users[0]->username;
echo $users[1]->username;
We will see many more examples of DQL in our project. But if you want more info right now, there is a long documentation chapter here.
toArray()
A Doctrine_Record object contains a lot of stuff behind the scenes. If you ever try to print_r() that object directly, you will see a long list of data.
However, when you simply want the data of the record, you can convert it to an array with the toArray() call.
$user_id = 1;
$u = Doctrine::getTable('User')->find($user_id);
$u_arr = $u->toArray();
print_r($u_arr);
What you get will be similar to what you get from a mysql_fetch_assoc() call.
3. UPDATE
The act of updating existing records.
Record Object
You can make direct changes to any Doctrine_Record object. Once you call save(), it will perform an UPDATE.
$user_id = 1;
$u = Doctrine::getTable('User')->find($user_id);
$u->password = 'newpassword';
$u->save();
DQL
When you want to update multiple rows at once, the preferred way is to use DQL.
$status = 'active';
$q = Doctrine_Query::create()
->update('User')
->set('status', '?', $status)
->where('id < 10');
$numrows = $q->execute();
echo "$numrows records updated";
The execute() call in this case returns the number of updated rows.
4. DELETE
The act of deleting records from the database.
Record Object
You can simply call the delete() method to delete a Doctrine_Record object directly.
$user_id = 1;
$u = Doctrine::getTable('User')->find($user_id);
$u->delete();
DQL
When deleting multiple records, use DQL.
$q = Doctrine_Query::create()
->delete('User')
->where('id < 10');
$numrows = $q->execute();
echo "$numrows records deleted";
The execute() method call will return the number of deleted records.
Stay Tuned
Today, we took a short break from coding our project and explored some important subjects regarding the usage of CRUD with Doctrine. What we learned today will be very useful as we continue building our project in the next tutorials.
See you next time!
#1 by bjorn on November 9th, 2009
| Quote
dataobjects ftw!
Thanks for this series, CI needs more of this stuff.
#2 by 3m masr on November 9th, 2009
| Quote
al salamo alikoum
this tutorial is really awesome
keep going !
#3 by zack on November 10th, 2009
| Quote
Bring it on!
#4 by esranull on November 10th, 2009
| Quote
işte bu ya sonunda derli toplu olarak bulabildim
#5 by christian reyes on November 18th, 2009
| Quote
nice article. finally something definitive in using doctrine. i’m using doctrine and kohana btw.
#6 by Daniel on December 2nd, 2009
| Quote
How can I sort the results using the findBy* or findAll methods? Or do i have to use DQL for that?
#7 by Burak on December 10th, 2009
| Quote
The only way I know is DQL.
#8 by piyush on January 5th, 2010
| Quote
i think it can be done in this way :
$records = Doctrine::getTable(’table_name’)->findByFieldname($value);
#9 by mehike on December 25th, 2009
| Quote
here is some intresting error. Doctrine works fine but smple select DQL query not works: I have user table in mysql DB
hasColumn(’username’, ’string’, 20, array(’unique’ => ‘true’));
$this->hasColumn(’password’, ’string’, 50);
$this->hasColumn(’email’, ’string’, 80, array(’unique’ => ‘true’));
$this->hasColumn(’activated’, ‘boolean’, null, array(’default’ => ‘0′));
$this->hasColumn(’act_lang’, ‘enum’, null,
array(’values’ => array(’english’, ‘russian’,'finland’),
‘default’ => ‘english’));
$this->hasColumn(’banned’, ‘boolean’, null, array(’default’ => ‘0′));
$this->hasColumn(’ban_reason’, ’string’, 255);
$this->hasColumn(’locked’, ‘boolean’, null, array(’default’ => ‘0′));
$this->hasColumn(’lock_reason’, ’string’, 100);
$this->hasColumn(’user_group_id’, ‘integer’, 10, array(
‘unsigned’ => true, ‘default’ => 1));
$this->hasColumn(’roll_id’, ‘integer’, 10, array(
‘unsigned’ => true, ‘default’ => 90
));
$this->hasColumn(’login_count’, ‘integer’, 10);
$this->hasColumn(’new_password_key’, ’string’, 50);
$this->hasColumn(’new_password_requested’, ‘timestamp’, 8);
$this->hasColumn(’new_email’, ’string’, 80);
$this->hasColumn(’new_email_key’, ’string’, 50);
$this->hasColumn(’last_ip’, ’string’, 40);
$this->hasColumn(’last_login’, ‘timestamp’);
$this->option(’type’, ‘InnoDB’);
$this->option(’collate’, ‘utf8_unicode_ci’);
$this->option(’charset’, ‘utf8′);
$this->index(’username’, array(
‘fields’ => array(’username’)
));
$this->index(’email’, array(
‘fields’ => array(’email’)
));
}
public function setUp() {
$this->setTableName(’user’);
$this->actAs(’Timestampable’);
$this->hasMutator(’password’, ‘_encrypt_password’);
$this->hasOne(’User_group’, array(
‘local’ => ‘user_group_id’,
‘foreign’ => ‘id’
));
$this->hasOne(’Roll’, array(
‘local’ => ‘roll_id’,
‘foreign’ => ‘id’
));
}
protected function _encrypt_password($value) {
$salt = ‘#*seCrEt!@-*%’;
$this->_set(’password’, md5($salt . $value));
}
}
and i created on user in my registration form with Doctrine
but if I want activate user in my activate function with Doctrine DQL I have error. This some query works in mysql command line. No mistakes in php cod.
DQL is (variables $uer_id and $new_email_key is fine and ok):
$q = Doctrine_Query::create()
->select(’u.activated, u.new_email_key, u.id’)
->from(’User u’)
->where(”u.id = $user_id”)
->andWhere(”u.new_email_key = ‘”.$new_email_key.”‘”)
->andWhere(’u.activated = false’)
->limit(1);
log_message(’debug’,'query for activate_one_user=’.$q->getSqlQuery());
$user= $q->execute();
and error is:
Fatal error: Call to a member function getOid() on a non-object in /opt/lampp/htdocs/CI-1.7.2/system/plugins/doctrine/lib/Doctrine/Collection.php on line 880
all versions is new: Doctrine, Codeigniter, PHP, MySql
if I use this DQL, its works fine:
$q = Doctrine_Query::create()
->update(’User’)
->set(’activated’, ‘?’, array(1))
->set(’new_email_key’, ‘?’, array(”))
->where(”id = $user_id”)
->andWhere(”new_email_key = ‘”.$new_email_key.”‘”)
->andWhere(’activated = false’);
log_message(’debug’,'query for activate_one_user=’.$q->getSqlQuery());
$numrows=$q->execute();
or this works to:
$usertable = Doctrine::getTable(’User’);
$user = $usertable->findOneByIdAndActivatedAndNew_email_key($user_id, false, $new_email_key);
what is wrong here?
#10 by Burak on December 26th, 2009
| Quote
I’m not sure. That getOid() stuff is being called internally.
You should post your question here: http://groups.google.com/group/doctrine-user
Doctrine developers help out there.
#11 by mehike on December 27th, 2009
| Quote
thanks, but I’m not user in that usegroup.
but I solved this problem-removed some relatonships in Doctine Models and now all works.
but thanks for answer.
very fine tutorials here!
#12 by mehike on December 25th, 2009
| Quote
and in log file is query – this works fine in command line mysql
DEBUG – 2009-12-25 09:51:10 –> query activate_one_user=SELECT u.id AS u__id, u.activated AS u__activated, u.new_email_key AS u__new_email_key FROM user u WHERE (u.id = 2 AND u.new_email_key = ‘bfbe1326ddcde01777b7242d7b941048′ AND u.activated = 0) LIMIT 1
#13 by Mourad on December 26th, 2009
| Quote
hi everyone !
thanks for those nice tuts Burak !
I faced something that made me crazy when executing this code :
$q = Doctrine_Query::create()
->SELECT(’username’)
->FROM(’User’);
$users = $q->execute();
foreach ($users as $user)
{
echo $user->email.”;
}
is that supposed to echo out the email field ?! since i don’t want to retrieve it
( ->SELECT(’username’) )
If Doctrine retrieve the hole object (all fields) from the db, why specifying the field on the SELECT statement ?
#14 by Burak on December 26th, 2009
| Quote
It is called “lazy loading”. As you access $user->email, Doctrine executes another query to fetch that value. If you put email in the SELECT statement, it will be fetched with the initial query, so Doctrine won’t generate an extra query when you try to access it.
#15 by Mourad on December 26th, 2009
| Quote
and when i use the tag for the returned Doctrine_Collection Object it’s clearly visible that [email] => Doctrine_Null Object ( ).
#16 by Mourad on December 27th, 2009
| Quote
I see. thanks for the info Burak.
#17 by Ivan Rivera on January 27th, 2010
| Quote
Thank U, this was very useful 4 me.
#18 by Mike on February 11th, 2010
| Quote
Hello,
I have a bit of a problem. Maybe you can shed some light on this.
Here is the run down.
I have an ITEM and an INVENTORY model. The INVENTORY can be associated with/have many ITEMS.
So my Item is setup like this
$this->hasOne(’Inventory’, array(
‘local’ => ‘inventory_id’,
‘foreign’ => ‘id’
));
And my Inventory is setup like this
$this->hasMany(’Item as Items’, array(
‘local’ => ‘id’,
‘foreign’ => ‘inventory_id’
));
Now this is the key. When the item gets added to the system it does not have an inventory associated with it. This is added later.
So when an item is added to the system I get the following error
General error: 1452 Cannot add or update a child row: a foreign key constraint fails (`testDB`.`item`, CONSTRAINT `item_inventory_id_inventory_id` FOREIGN KEY (`inventory_id`) REFERENCES `inventory` (`id`))’
The only way to fix this is to go into the db after the tables have been created and remove the InnoDB foreign key reference to inventory.id from the inventory_id column in the item table.
I really hope that wasn’t too confusing. I am stumped on why Doctrine is not allowing me to add an item without an inventory.
THANKS!
#19 by Mark on February 12th, 2010
| Quote
Hi Mike, I’m by no means an expert, but I think that that is expected behavior for a foreign key. this is a snippet from mysql documentation:
InnoDB rejects any INSERT or UPDATE operation that attempts to create a foreign key value in a child table if there is no a matching candidate key value in the parent table.
#20 by Mike on February 12th, 2010
| Quote
Hi,
Thanks for the reply. I figured that was the case.
I guess I am wondering why doctrine would allow us to set up our tables in this fashion, and if by removing the foreign key manually I am causing any conflicts.
Maybe it is just that allowing doctrine to create your tables for you isn’t fool proof.
#21 by Mark on February 12th, 2010
| Quote
I think that’s standard database normalization behavior.
#22 by Burak on February 13th, 2010
| Quote
Try setting the inventory_id column to NULL. I am guessing it is NOT NULL right now because in the doctrine_pi.php file I set that as default.
In your setTableDefinition() method, do this:
$this->hasColumn(’inventory_id’, ‘integer’, 4,
array(’notnull’ => false)
);
#23 by Mike on February 14th, 2010
| Quote
That does the trick. Thanks.
#24 by Harshit on March 23rd, 2010
| Quote
I am using two model file login_model.php(User_login) and registration_model.php(Class Name:User_registration)
both of class using same table ‘tbl_user’.But when i am calling login function of User_login class it gives me followinf error.
Uncaught exception ‘Doctrine_Exception’ with message ‘Couldn’t find class tbl_user’ in C:\wamp\www\UrBizPartner\system\application\plugins\doctrine\lib\Doctrine\Table.php:290 Stack trace: #0 C:\wamp\www\UrBizPartner\system\application\plugins\doctrine\lib\Doctrine\Table.php(240): Doctrine_Table->initDefinition() #1 C:\wamp\www\UrBizPartner\system\application\plugins\doctrine\lib\Doctrine\Connection.php(1127): Doctrine_Table->__construct(’tbl_user’, Object(Doctrine_Connection_Mysql), true) #2 C:\wamp\www\UrBizPartner\system\application\plugins\doctrine\lib\Doctrine.php(954): Doctrine_Connection->getTable(’tbl_user’) #3 C:\wamp\www\UrBizPartner\system\application\models\login_model.php(42): Doctrine::getTable(’tbl_user’) #4 C:\wamp\www\UrBizPartner\system\application\controllers\login.php(42): Tbl_use->login(’harshit_cool90@…’, ‘frustrate’) #5 [internal function]: Login->submit() #6 C:\wamp\www\UrBizPartner\system\codeigniter\CodeIgniter.php(236): call_user_func_array(Array, Array) #7 C:\wamp\www\UrBizPartner in C:\wamp\www\UrBizPartner\system\application\plugins\doctrine\lib\Doctrine\Table.php on line 290
if we call saveinfo() in user_registration class then it work fine…PLease Help…….
#25 by Anthony on April 24th, 2010
| Quote
I really appreciate your tutorials. They have helped me alot. However, I can’t help but feel that spending time learning how to use Doctrine was a mistake. On Doctrine’s website, many of the links for their tutorials are broken and its difficult getting help online other than here. I’m still awaiting approval to join the Doctrine group. Im beginning to regret spending 2 days on Doctrine.
#26 by Anthony on April 24th, 2010
| Quote
I figured out what was causing my error: I left out something while creating the database. Everything works fine now. Thanks alot!
#27 by david on May 3rd, 2010
| Quote
Hi Burak et al…
Thanks for the amazing tutorials. I went through them a while back and have been happily using Doctrine and Codeigniter together since! I am now have a bit of a troublesome issue, and thought I might find some pointers here…
essentially, I am trying to delete a record and its related records and receive this error:
Fatal error: Uncaught exception 'Doctrine_Connection_Mysql_Exception' with message 'SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`machaikbids`.`vehicle`, CONSTRAINT `vehicle_auction_id_auction_id` FOREIGN KEY (`auction_id`) REFERENCES `auction` (`id`))' in /var/www/machaik/application/plugins/doctrine/lib/Doctrine/Connection.php:1082 Stack trace: #0 /var/www/machaik/application/plugins/doctrine/lib/Doctrine/Connection/Statement.php(269): Doctrine_Connection->rethrowException(Object(PDOException), Object(Doctrine_Connection_Statement)) #1 /var/www/machaik/application/plugins/doctrine/lib/Doctrine/Connection.php(1042): Doctrine_Connection_Statement->execute(Array) #2 /var/www/machaik/application/plugins/doctrine/lib/Doctrine/Connection/UnitOfWork.php(251): Doctrine_Connection->exec('DELETE FROM auc...', Array) #3 /var/www/machaik/application/plugins/doctrine/lib/Doctrine/Connection/UnitOfWork.php(168): Doctrine_Connection_UnitOfWork->_executeDele in /var/www/machaik/application/plugins/doctrine/lib/Doctrine/Connection.php on line 1082I found some info that leads me to believe I need to have ‘onDelete’ set to ‘CASCADE’ somewhere in the model definition but am having no luck with this…
Perhaps someone here can help me along?
Thanks!!!
#28 by david on May 9th, 2010
| Quote
Well, I found Burak stating that he placed this:
in the doctrine_tools.php file used in this series of tutorials because he need to get around some foreign key check problems. I added that line in my controller function above $object->delete() comamnd and now I no longer have any problems deleting! : )
i do wonder if it will have unintended consequences however o_O ?
If anyone reads this and would like to pass any warnings my way, please do so at david AT tomotechi DOT com.
Thanks!!!
#29 by david on May 9th, 2010
| Quote
Need to add a bit more info, as the above is incomplete at best. My original suspicion about ‘onDelete’ was correct, although I didn’t have any luck with it originally because I was specifying it on the parent side of the relation in the model setup function.
adding the following to the children like this finally does work:
Parent:
$this->hasMany('vehicle', array( 'local' => 'id', 'foreign' => 'auction_id' )); $this->hasMany('bid', array( 'local' => 'id', 'foreign' => 'auction_id' ));Children:
$this->hasOne('auction', array( 'local' => 'auction_id', 'foreign' => 'id', 'onDelete' => 'CASCADE' )); $this->hasOne('vehicle', array( 'local' => 'vehicle_id', 'foreign' => 'id', 'onDelete' => 'CASCADE' )); // and $this->hasOne('auction', array( 'local' => 'auction_id', 'foreign' => 'id', 'onDelete' => 'CASCADE' ));sorry for any confusion, but i hope the extra information helps out.
#30 by Robin Mitra on May 27th, 2010
| Quote
Thanks for the pointer! It indirectly helped me find other issues as well with my model classes (many-to-many)
Pingback: ci + doctrine and updating database fields - xBlurb
#31 by david on May 27th, 2010
| Quote
glad it helped. : )
cheers!