- CodeIgniter 1.7
- David Upton Jose Argudo Blanco
- 3533字
- 2021-04-01 13:45:31
Active Record
Active Record is a "design pattern"—another of those highly abstract systems like MVC, which provide templates for solving common coding problems. In itself, it isn't code, it is just a pattern for code. There are several different interpretations of it. At its core is the creation of a relationship between your database and an object, every time you write a query. Typically, each table is a class (represented by our models), and each single row becomes an object. All the things you might want to do with a table row, for example, create it, read it, update it, or delete it, become "methods", which that object inherits from its class. CakePHP is built around the Active Record pattern, and so is CI—although the exact implementation in the two frameworks seems to have differences.
For example, models
in CakePHP are associated to tables
, every table has a corresponding model representing it. CodeIgniter gives you a little more freedom here; your models don't need to be associated directly to tables. Though by experience we can say, most of the times that association simply happens to occur. But enough theory for now—what does it mean? Well, simple and clear code statements, if you don't mind putting arrows in them.
Advantages of using the Active Record class
Active Record saves your time, brings in automatic functionality that you don't have to think about, and makes SQL statements easy to understand. One thing you should take into account, should you need to change your RDBMS (database management system) from MySQL to Postgres (or another of the databases supported by CodeIgniter) is that you wouldn't need to rewrite your Active Record queries as CodeIgniter translates them for your choice of RDBMS. This will save us time and code rewrites.
When you write a normal database query in PHP, you must write a connection to the database each time. With CI, you connect once to the database, by putting the following line in the constructor function of each controller or model:
$this->load->database();
Note
Remember, you can autoload the database
library in order not to have to write that line of code in every function of every controller. If you are building an application that uses the database intensively, it would be a good idea to do so. It is very easy, you only need to add the database
library to the libraries
array in /www/codeigniter/application/config/autoload.php
, like this:
$autoload['libraries'] = array('database')
;
Once you've done this, however, many queries you make in that controller or model
, you don't have to repeat the connection. You set up the database details in the config files as we saw earlier in this chapter. Once again, this makes it easier to update your site, if you ever change the database name, password, or location.
Once you've connected to the database, CI's Active Record syntax brings hidden code with it. For instance, if you enter the following insert
query:
$data = array( 'title' => $title, 'name' => $name, 'date' => $date ); $this->db->insert('mytable', $data);
The values you insert escape behind the scenes by using this code:
function escape($str) { switch (gettype($str)) { case 'string': $str = "'".$this->escape_str($str)."'"; break; case 'boolean': $str = ($str === FALSE) ? 0 : 1; break; default : $str = ($str === NULL) ? 'NULL' : $str; break; } return $str; }
In other words, the CI framework is making your code more robust. Now, let's look at how it works. Firstly, connecting to the database is very simple. In classic PHP, you might say something like this:
$connection = mysql_connect("localhost","fred","12345"); mysql_select_db("websites", $connection); $result = mysql_query ("SELECT * FROM sites", $connection); while ($row = mysql_fetch_array($result, MYSQL_NUM)) { foreach ($row as $attribute) print "{$attribute[1]} "; }
In other words, you have to restate the host username and password, make a connection, and then select the database from that connection. Only then do you get to the actual query. CI replaces the connection stuff with one line:
$this->load->database();
You need to put it once in each controller, model, and class constructor that you write. If you feel it's necessary, autoload it. After that, in each function within those controllers, and so on, you go straight into your query. The connection information is stored in your database.php
file in the config
folder, and CI goes and looks it up there, each time.
So, in each CI function, you go straight to your query. The previous query written in CI comes out as:
$query = $this->db->get('sites'); foreach ($query->result() as $row) { print $row->url }
Simple, isn't it?
But as always, in CodeIgniter there are many hidden functionalities in case you need them. For example, if you need to connect to another database instead of the default one, you could do it very easily as:
$this->load->database('name');
Where name
equals to what you have in the database.php
config file:
$db['name']['hostname'] = "localhost"; $db['name']['username'] = "root"; $db['name']['password'] = "root"; $db['name']['database'] = "websites"; $db['name']['dbdriver'] = "mysql"; $db['name']['dbprefix'] = ""; $db['name']['pconnect'] = TRUE; $db['name']['db_debug'] = TRUE; $db['name']['cache_on'] = FALSE; $db['name']['cachedir'] = ""; $db['name']['char_set'] = "utf8"; $db['name']['dbcollat'] = "utf8_general_ci";
What if you want to connect to two or more databases, you can do that very easily too:
$database1 = $this->load->database('name', TRUE); $database2 = $this->load->database('name2', TRUE);
The TRUE
parameter indicates to the function to return the database
object. Once you have done so you will be able to query both databases, but in a slightly different way, instead of using:
$this->db->query();
You will use:
$database1->query();
In the rest of this chapter you will see ways of making different queries, making them more specific.
The most common query that we'll write simply retrieves information from the database according to our criteria. The basic instruction to perform a read query is:
$query = $this->db->get('sites');
This is a SELECT *
query on the sites
table—in other words, it retrieves all the fields. If you prefer to specify the target table (sites) in a separate line, you can do so in this way:
$this->db->from('sites'); $query = $this->db->get();
If you want to select
or limit
the number of fields retrieved, rather than get them all, use this instruction:
$this->db->select('url,name,clientid'); $query = $this->db->get('sites');
You may want to present the results in a particular order, say by the site name, in which case you insert (before the $this->db->get
line):
$this->db->orderby("name", "desc");
The parameter desc
means in descending order. You can also choose asc
(ascending) or rand
(random).
You may also want to limit the number of results your query display. Say you want only the first five results. In this case insert:
$this->db->limit(5);
Of course, in most queries, you're not likely to ask for every record in the table. The power of databases depends on their ability to select—to pick out the one piece of data you want from the piles of stuff you don't. This is usually done by a where
statement that CI expresses in this way:
$this->db->where('clientid', '1');
This statement would find all websites linked to the client whose ID is 1
. But that's not of much help to us. We don't want to remember all the IDs in our people
table. As humans, we prefer to remember human names, so we need to link in the people
table:
$this->db->from('sites'); $this->db->join('people', 'sites.peopleid = people.id');
For each peopleid
in the sites
table, look up the information against that ID in the people
table as well.
Note
Note the SQL convention: If a field name may be ambiguous between two tables, you reference it with the table name first, then a period, and then the field name. So, sites.peopleid
means the peopleid
field in the sites
table. It's a good habit to make your meaning explicit, and CI syntax happily accepts the full names.
You can play around with the syntax of where
statements. For instance, you can:
- Add negation operators
$this->db->where('url !=','www.mysite.com' );
- Add comparison operators
$this->db->where('id >','3' );
- Add combine statements (
"WHERE… AND…"
)$this->db->where('url !=','www.mysite.com'); $this->db->where('id >', '3');
You can also use $this->db->or_where()
to search for alternatives ("WHERE … OR"):
$this->db->where('url !=','www.mysite.com' ); $this->db->or_where('url !=','www.anothersite.com' );
So let's say we've built a query like this:
$this->db->select('url,name,clientid,people.surname AS client'); $this->db->where('clientid', '3'); $this->db->limit(5); $this->db->from('sites'); $this->db->join('people', 'sites.clientid = people.id'); $this->db->order_by("name", "desc"); $query = $this->db->get();
The previous snippet should give you the first five websites (ordered by name) belonging to client number 3, and fetch the client's surname, as well as his/her ID number!
A hidden benefit of using Active Record is that data coming in from users is automatically escaped, so you don't have to worry about putting quotes around it. This applies to functions such as $this->db->where()
; and also to the data creation and update statements described in the sections that follow.
If you don't use Active Record, it is advisable that you escape your data before sending it to your database. When using Active Record this is done automatically for you, but if you aren't using it, you have to do it manually, using the escape
function:
$this->db->escape();
For example:
$sql = "SELECT * FROM people WHERE uname = "'".$this->db->escape($uname)."'"; $this->db->query($sql);
It's an easy way of giving your queries more security if you can't or don't want to use Active Record. There is another way of doing so—query binding. Query binding will escape your data before it is passed to the database. Let's see an example of query binding:
$sql = "SELECT * FROM people WHERE id = ? AND uname = ? AND status = ?"; $this->db->query($sql, array(1, 'Jose', 1));
Now it is for you to decide which method to use. At least you can't say CodeIgniter doesn't gives you choices!
Showing database query results in CI is quite simple. Define your query as you did previously, ending in:
$query = $this->db->get();
Then, if there are multiple results, they can be returned as an array of $row
objects, through which you iterate with a foreach
loop:
foreach ($query->result() as $row) { print $row->url; print $row->name; print $row->client; }
If you only want a single result, it can be returned as an object or as a $row
array, as in the following example:
if ($query->num_rows() > 0) { $row = $query->row_array(); print $row['url']; print $row['name']; print $row['client']; }
Personally, I prefer the object syntax to the array—less typing!
When you follow the MVC pattern, you will usually want to keep your queries and database interactions in models, and display the information through views.
Active Record has three functions that help you to create new entries in your database. They are $this->db->insert(), $this->db->update()
, and $this->db->set()
.
Let's see the difference between a "create" and an "update" query. When you create a new record, there is no reference to any existing record, you write a new one. When you update, there is an existing record and you are changing it. So, in the second case you have to specify, which record you are changing. In both cases you have to set the values that you want to keep in the database after your query. Values you don't set will be left unaltered. If they didn't exist before, they will still be "null" after your query.
CI allows you to set the values either using an array or $this->db-set()
. The only difference is the syntax. So, let's add a line to your sites
table in the websites
database. We've already connected to this database in our controller. The controller's constructor function included the line:
$this->load->database();
You want to add a new site, which has a URL, a name, a type, and a client ID number. As an array, this can be written as:
$data = array( 'url' => 'www.mynewclient.com', 'name' => 'BigCo Inc', 'clientid' => '33', 'type' => 'dynamic', );
To add that to the sites
table, we follow it with:
$this->db->insert('sites', $data);
Alternatively, you could set each value using $this->db->set()
:
$this->db->set('url', 'www.mynewclinet.com'); $this->db->set('name', 'BigCo Inc'); $this->db->set('clientid', '33'); $this->db->set('type', 'dynamic'); $this->db->insert('sites');
Of these two, we prefer the first one as it seems clearer. However, there is no difference between them, use the one you are comfortable with.
If you are updating an existing record, then again you can either create an array, or use $this->db->set()
. But there are two differences. Firstly, you have to specify the record you want to update. Second, you need to use $this->db->update()
. If you want to update a record (say the record with its id
field set to 1
) in your sites
table, using the data set in your $data
array as you did earlier, the syntax is:
$this->db->where('id', '1'); $this->db->update('sites', $data);
You can also set the information using $this->db->set()
, as you saw earlier. CI gives you several functions to check what the database has done. Most usefully:
$this->db->affected_rows();
This should return "1" after the insert or update statement—it might show more rows if we were to alter several rows of data at one time. You can use it to check that the operation has done what you expected.
You've noticed that we didn't set an ID field when we created a new record. That's because you set the database to populate the ID field automatically when a new record is added. You have to specify an ID when you update an existing record, otherwise the database doesn't know which one to alter.
However, if you're generating a new record, you don't know the ID number until you've generated it. Then, if you need to refer to the new record, you can get the new ID number using:
$new_id_number = $this->db->insert_id();
For a little more peace of mind remember that CI's Active Record functions, including $this->db->insert()
and $this->db->update()
, automatically escape data passed to them as input.
From version 1.5, CI also includes support for transactions—linking two or more database actions together so that either all succeed or all fail. This is essential in double-entry book keeping applications and many commercial sites. For instance, if you are selling theatre tickets you can record receiving a payment in one transaction, and then allocate a seat to the customer in another. If your system fails, after doing the first database operation and before doing the second, you may end up with an angry customer—who has been charged, but has not had a seat reserved.
CI, now makes it much simpler to link two or more database operations into one transaction. So, if they all succeed, the transaction is "committed". If one or more fails, the transaction is "rolled back". Transactions are enabled by default; you only have to indicate to CodeIgniter that you want to use them:
$this->db->trans_start(); $this->db->query('first sql query'); $this->db->query('second sql query'); $this->db->query('third sql query'); $this->db->trans_complete();
These three queries need to be executed correctly, or all three of them will be rolled back. This is useful for shopping carts, or any query that needs another one to be successful.
Note
More information about CodeIgniter transactions can be found at:
http://codeigniter.com/user_guide/database/transactions.html
Delete queries are perhaps the simplest to describe. All you need is the name of the table and the ID number of the record to delete. Let's say, you want to delete a record in the sites
table with the id
number 2
:
$this->db->where('id', '2'); $this->db->delete('sites');
Please remember to make sure that there is a valid value in the where
clause, or you may delete the whole table! Neither the authors nor Packt Publishing will accept any liability if….
Mixing Active Record and "classic" styles
CI doesn't insist that you use Active Record. You can also use CI to issue straight SQL queries. For instance, assuming you loaded the database in your constructor, you can still write queries such as:
$this->db->query("SELECT id, name, url FROM sites WHERE `type` = 'dynamic'");
Active Record is easy to use. Conceptually, setting a query in an array makes it easier to see and manipulate it as an entity than writing it in SQL syntax. It's slightly more verbose, but clearly structured. It automatically escapes data and may be more portable. It also minimizes typing errors with commas and quotes.
However, there are a few cases where you may have to resort to the original SQL. For example, you might want to do complex joins or need to use multiple WHERE
conditions. If you want to find the websites associated with client 3, but only those of two specific types, you may need to put brackets around the SQL, to make sure the query is correctly interpreted.
In cases like these, you can always write the SQL as a string, put it in a variable, and use the variable in CI's $this->db->where()
function, as follows:
$condition = "client ='3' AND (type ='dynamic' OR type='static')"; $this->db->where($condition);
Without the brackets this is ambiguous. Do you mean:
(client='3' AND type = 'dynamic') OR type = 'static'
Or:
client='3' AND (type = 'dynamic' OR type = 'static')
Well, of course it's obvious, but the machine usually guesses wrong. Be careful with the syntax of $condition
. The actual SQL query is:
client='3' AND (type = 'dynamic' OR type = 'static')
The double quotes come from the variable assignment:
$condition = " ";
It's easy to get your single and double quotes confused. Some of the CI expressions quoted earlier, such as $this->db->affected_rows()
, are not a part of its Active Record model. But they can be mixed up easily.
Dealing with complex queries
There will be some queries that will need some extra effort to translate to Active Record, let's see this one:
SELECT `M`.`name`, `S`.`brand_id`, count(S.brand_id) as votes FROM (`selections` as S, `brands` as M) WHERE `M`.`id` = S.brand_id AND `S`.`id_usu_facebook` IN ('1704982171', '1428056700') GROUP BY `S`.`brand_id` ORDER BY count(S.brand_id) DESC LIMIT 0,6
If you start translating this query to an Active Record one, you may come across errors. But how can you solve them, if you are not seeing the SQL query that is being generated?
There is a way in which you can see the SQL queries that are generated and run when our application is executed. In order to do so, you need to enable the CI profiler, such as:
$this->output->enable_profiler(TRUE);
Now, when you load your application, all queries and generated SQL are output to your browser's screen. You will then be able to see what SQL your Active Record query is generating, making it easier for you to check errors. Following with the example, you can create this Active Record query:
$this->db->select('M.name, S.brand_id, count(S.brand_id) as votes'); $this->db->from('selections as S, brands as M'); $this->db->where('M.id = S.brand_id'); $this->db->where_in('S.id_usu_facebook ', $users); $this->db->group_by('S.brand_id'); $this->db->order_by('count(S.brand_id)', 'desc'); $this->db->limit(0, 6); $query = $this->db->get();
This looks correct, but it is not. The SQL query that it will generate is this one:
SELECT `M`.`name`, `S`.`brand_id`, count(S.brand_id) as votes FROM (`selections` as S, `brands` as M) WHERE `M`.`id` = S.brand_id AND `S`.`id_usu_facebook` IN ('1704982171', '1428056700') GROUP BY `S`.`brand_id` ORDER BY count(S.brand_id) desc LIMIT 6,0
See the difference? In our starting query we had the limit 0,6
and the generated one has limit 6,0
. This query won't generate any errors, but it will produce bad results that would be very difficult to trace without the help of the profiler. Now, if we change this:
$this->db->limit(0, 6);
To:
$this->db->limit(6, 0);
Our query will be correct this time. With the help of the profiler you will notice this earlier than without it. So if you are having problems with your queries don't forget to use the profile in order to see what SQL your Active Record is generating.
Another method that can help you while working with databases is last_query
, which can be called using $this->db->last_query()
. This method will return the SQL for the last query, the SQL string, not the result. This works somewhat like the profiler, showing the SQL equivalent to the Active Record but only for the query you want, and not for all, unlike the profiler.
Take our last query, for example:
$this->db->select('M.name, S.brand_id, count(S.brand_id) as votes'); $this->db->from('selections as S, brands as M'); $this->db->where('M.id = S.brand_id'); $this->db->where_in('S.id_usu_facebook ', $users); $this->db->group_by('S.brand_id'); $this->db->order_by('count(S.brand_id)', 'desc'); $this->db->limit(0, 6); $query = $this->db->get(); $last_query = $this->db->last_query();
If you "echo" the $last_query
variable you will get:
SELECT `M`.`name`, `S`.`brand_id`, count(S.brand_id) as votes FROM (`selections` as S, `brands` as M) WHERE `M`.`id` = S.brand_id AND `S`.`id_usu_facebook` IN ('1704982171', '1428056700') GROUP BY `S`.`brand_id` ORDER BY count(S.brand_id) desc LIMIT 6,0
It is useful if you don't want to use the profiler, but you need to check one query in particular.
- PS是這樣玩的:輕松掌握 Photoshop 通關秘籍
- AI繪畫教程:Midjourney使用方法與技巧從入門到精通
- 創意UI:Photoshop玩轉移動UI設計
- PPT設計實用教程
- Authorware應用案例教程
- eZ Publish 4: Enterprise Web Sites Step/by/Step
- Oracle VM Manager 2.1.2
- 跟儲君老師學Excel極簡思維
- Ogre 3D 1.7 Beginner's Guide
- Photoshop影視動漫角色繪制技法精解
- 中文版3ds Max 2016實用教程
- 邊做邊學:平面廣告設計與制作(Photoshop 2020+Illustrator 2020·第3版·微課版)
- Photoshop 2020實戰從入門到精通(超值版)
- 中文版Flash CC實例教程
- Troux Enterprise Architecture Solutions