Pros and cons of building queries with Active Record - php

I have this query which I want to run in my PHP application back end. Notionally, sheet is a DB that keeps track of all the sheets we have. Purchases is a DB that keeps track of which users have access to which sheet. The query I want to run is given a user's id, I can get all the sheets that they should have access to. In query form:
select distinct s.wsid, s.name from sheets s, purchases p where
s.wsid = p.wsid AND p.uid = *value*;
where value is something input by the application
The way I see it there are two ways to go about getting this to work in the back end.
Option 1)
public function getPurchasedSheets($uid){
if( is_numeric($uid) ){ //check against injections
$query = "select distinct s.wsid, s.name from sheets s, purchases p
where s.wsid = p.wsid AND p.uid = ".$uid.";" ;
return $this->db->query($query);
} else {
return NULL; //or false not quite sure how typing works in PHP
}
}
Option 2)
public function getPurchasedSheets($uid){
if( is_numeric($uid) ){
$this->db->select('wsid, name');
$this->db->distinct();
$this->db->from('purchases');
//not sure which order the join works in...
$this->db->join('sheets', 'sheets.wsid = purchases.wsid');
$this->db->where('uid ='.$uid);
return $this->db->get();
} else {
return NULL;
}
}
Source for all the CodeIgniter Active Record commands:
codeigniter.com/user_guide/database/active_record.html
Is there some sort of performance or security difference from doing thing one way or another? Doing it the second way seems so much more confusing to me... This is compounded a little bit because I am not sure how to do referential disambiguation in this style of coding because purchases and sheets both have a uid field but they mean different things (in addition to not being very familiar with the SQL join command in the first place.). Uid (user id) in purchases means that user has purchased that sheet, while Uid in sheets denotes which user owns that sheet.
TL,DR: Basically, I am asking is there a reason I should sink time into looking how to do things the option 2 way?

The main benefits are:
Abstraction from the database engine, where the library can take care
of database-specific SQL syntax differences for you. Relevant if you
ever have/want to change the database you're working with. In theory,
the second form should still just work.
The 'Active Record' syntax
automatically escapes parameters for you.
Readability, although
that's a matter of taste.
Incidentally, if you're in a PHP 5 environment, the library supports method chaining:
if( is_numeric($uid) ){
return $this->db->select('wsid, name')
->distinct()
->from('purchases')
->join('sheets', 'sheets.wsid = purchases.wsid')
->where('uid ='.$uid)
->get();
// nb. didn't check your join() syntax, either :)
}
Probably off-topic: CodeIgniter's Active Record is more of a query builder than an implementation of Active Record. In case you're wondering. When viewed as a query builder, it makes a bit more sense ;) FWIW, I do love CodeIgniter. I jest from affection.

Query binding is the easiest to implement and does the same thing query building does - and doesn't limit you as much as you will find building does.
$query = $this->db->query('SELECT something FROM table WHERE name1=? AND name2=?', array($name1, $name2);
$result = $query->result();
http://codeigniter.com/user_guide/database/queries.html

Related

how to insert unique codes in a field with laravel?

my problem is that with a job (done in laravel 5.7) I need to change the codes of some promotions every day, the problem with the code that I have made changes the promotion code but it is the same for everyone, and I need it different for each of the promotions.
my code
DB::table('promociones')->update(['codigo_promocion'=>str_random(4)]);
If you don't have a lot of data like thousands of rows to update you can use a loop for it.
Using Eloquent:
$promociones = Promociones::all();
foreach($promociones as $promocion) {
$promocion->codigo_promocion = $this->generateUniqueString();
$promocion->save();
}
or by using query builder:
$promociones = DB::table('promociones')->get();
forea ch($promociones as $promocion) {
DB::table('promociones')
->where('id', $promocion->id)
->update(['codigo_promocion'=> $this->generateUniqueString()]);
}
And the generateUniqueString should check if the string is already inserted in the database:
private function generateUniqueString()
{
while(true) {
$randomString = str_random(4);
$doesCodeExist = DB::table('promociones')
->where('codigo_promocion', $randomString)
->count();
if (! $doesCodeExist) {
return $randomString;
}
}
}
Use where() if you need to filter the data.
Keep in mind if you have a lot of data you should consider using an approach like queues for example.
A simple way to handle this would be looping:
$promotions = Promocione::where(...)->get();
// Note, replace `...` with logic to target specific codes that need to be changed today.
foreach($promotions AS $promotion){
$promotion->codigo_promocion = str_random(4);
$promotion->save();
}
This assumes you have a Promocione model, and can be very performance intensive depending on the number of records in the database. Also, str_random(4) doesn't guaranteed a random value (in comparison to other uses of str_random(4) in the same loop), nor does it provide a large pool of random values, so you'll likely end up with duplicates. You can query for existing duplicates while looping, and generate a new code if you find one, but as you exhaust your pool of str_random(4) codes, this process will "lock up" to the eventually point of infinite execution.

Laravel mysql select result

I am trying to add a feature to a website built with Laravel.
There is a table containing vote numbers and user. I want to get the total points a user has in a certain category. I do not have any PHP or Laravel experience but said I would give this a shot.
$votes1 = UserVotes::select ('select vote from user_votes where feedback_id = ? and feedback_type = 1', Auth::user()->id);
This should return an object containing the vote amount. I want to interrogate the the object to check if the vote number is above a certain amount and then do something based on that being the case or not.
if vote > 50{
//do stuff
}
foreach ($votes1 as $vote1) {
echo $vote1->vote;
}
The query should return 1. I have verified this by querying the database, so the problem is with my understanding of Laravel or php. What I am doing wrong?
You don't need to construct your own SQL statement; Eloquent will do that for you.
If your models are set up in the default way, your query would look something like:
$votes = UserVotes::where('feedback_id', Auth::user()->id)
->where('feedback_type', 1)
->get();
You can then iterate over that as normal.
Additionally, if there is a relationship set up with the user model you could do something like
$votes = Auth::user()->votes()
->where('feedback_type', 1)
->get();
Check out the documentation here: http://laravel.com/docs/4.2/eloquent
assuming UsersVotes extends Model, here's how you should do it:
UsersVotes::select('vote')->where('feedback_type', 1)->where('feedback_id', Auth::user()->id)-get();

more efficient way of working with YII? Querying the database

This is a difficult question to ask as the code I have is working fine. Im just learning the YII platform and my issue isnt that I cant get what I want working, but moreso if there is a better way of doing this that takes advantage of the YII platform and its classes.
Basically I have a webstore using a platform called Lightspeed which uses the YII platform.
In the product detail section I am looking to pull its related products. Thankfully Lightspeed has the tables in place for this already (which gives me more reason to think I am doing this wrong).
Right now what Im doing seems a little hard coded.
In my view I have this to get the products...
$related_products = Product::GetRelatedProducts();
I have nothing in my controller, and in my model I have this..
public function getRelatedProducts()
{
$rawData=Yii::app()->db->createCommand('SELECT * FROM xlsws_product as Product LEFT JOIN xlsws_product_related as ProductRelated ON ProductRelated.related_id=Product.id WHERE ProductRelated.related_id=Product.id ')->queryAll();
return $rawData;
}
As I said there is nothing wrong with this code, but I see so much functionality in place with all the other queries in the model that it makes me think Im doing this incorrectly.
Examples include..
protected function getSliderCriteria($autoadd=0)
{
$criteria = new CDbCriteria();
$criteria->distinct = true;
$criteria->alias = 'Product';
$criteria->join='LEFT JOIN '.ProductRelated::model()->tableName().' as ProductRelated ON ProductRelated.related_id=Product.id';
if (_xls_get_conf('INVENTORY_OUT_ALLOW_ADD',0)==Product::InventoryMakeDisappear)
$criteria->condition = 'ProductRelated.product_id=:id AND inventory_avail>0 AND web=1 AND autoadd='.$autoadd.' AND parent IS NULL';
else
$criteria->condition = 'ProductRelated.product_id=:id AND web=1 AND autoadd='.$autoadd.' AND parent IS NULL';
$criteria->params = array(':id'=>$this->id);
$criteria->limit = _xls_get_conf('MAX_PRODUCTS_IN_SLIDER',64);
$criteria->order = 'Product.id DESC';
return $criteria;
}
Thats just an example of a widget that seems to use this data (although Im unsure how that data turns into arrays, as when I print out $criteria I get arrays containing query commands.
Let me know if you need more clarification on what Im looking for.
You're right that you're not leveraging Yii. Yii (and other MVC frameworks), abstract the database layer out into a model.
The getSliderCriteria() you show above is an example of building criteria to refine the interactions with the model.
What you should try and figure out is what model represents the data you're looking for in Lightspeed, then building and applying the criteria to it.
In most modern frameworks, you shouldn't be writing much (if any) raw SQL . . .

SQL SELECT * but with a twist - CodeIgniter

OK lets say I want to select a number of columns from a database table, but I won't know what those columns are in the method. I could pass them in, but it could be more or less depending on the method calling the database method.
A quick fix would be SELECT *, but I understand that this is bad and can cause more data to be returned than is necessary, and I definitely don't need all the data from that table.
So I am using CodeIgniter and prepared statements to do this, and below is what I have currently (it works, just point that out).
function get_pages() {
$this->db->select('pages.id, pages.title, pages.on_nav, pages.date_added, admin.first_name, admin.last_name')
->from('pages, admin')
->where('pages.admin_id = admin.id')
->order_by('pages.id', 'ASC');
$query = $this->db->get();
return $query->result();
}
It's a simple function, but at the moment limited to getting only 'pages'. I want to convert this to work with getting from other tables too. What is the best way?
Many thanks in advance.
EDIT In CodeIgniter I have many Controllers. One for 'pages', one for 'products', one for 'news' and on and on. I don't want to create a single database query method in my model for each controller.
i think the desire to not have 4 methods is misguided. if you don't have the information in the method, you'll have to pass it in. so you could either pass in a string with the table you want and switch over that changing the query based on the table name, or pass in all of the necessary parts of the query. this would include table name, criteria column, criteria, and columns to select. and you'd need to pass that information in every time you called the function. neither of those two methods are really going to save you much code, and they're both less readable than a function for each purpose.
The entire idea with models to put your specific queries to the persistence layer in there. Using a generic catch-all method can be disastrous and hard to test. You should shape your model around the problem you're trying to solve.
This makes it much cleaner and easier to work with. At the same time you must also avoid the common trap of over-sizing models. Each model should follow the SRP. Try and separate concerns so that in your controller, you can easily see state changes.
Does that make sense or am I just rambling...?
In your model:
function get_pages($table_source) {
$this->db->select($table_source.".id"); // or $this->db->select('id');
// for instance, if one of your $table_source ="users" and there is no 'title' column you can write
if($table_source!='users') $this->db->select('title');
$this->db->select('on_nav');
$this->db->select('date_added');
$this->db->select('admin.first_name');
$this->db->select('admin.last_name');
$this->db->join('admin','admin.id = '.$table_source.'.admin_id')
$this->db->order_by('pages.id', 'ASC');
$query = $this->db->get($table_source);
return $query->result_array();
}
In your controller:
function all_tables_info() {
$tables = array("pages","users","customers");
$i=0;
foreach($tables as $table) {
$data[$i++]=$this->your_Model->get_pages($table);
}
//do somthing with $data
}

Database design: Matching sql database keys to php constants?

Well this is a simple design question I've wondered about many times and never found a satisfying solution for. My example is with php-sql, but this certainly applies to other languages too.
I have a small database table containing only very few entries, and that almost never needs updating. eg this usertype table:
usertype_id (primary key) | name | description
---------------------------+------------+-------------------
1 | 'admin' | 'Administrator'
2 | 'reguser' | 'Registered user'
3 | 'guest' | 'Guest'
Now in the php code, I often have to check or compare the type of user I'm dealing with. Since the user types are stored in the database, I can either:
1) Select * from the usertype table at class instantiation, and store it in an array.
Then all the ids are available to the code, and I can do a simple select to get the rows I need. This solution requires an array and a db query every time the class is instantiated.
$query = "SELECT info, foo FROM user WHERE usertype_id = ".$usertypes['admin'];
2) Use the name column to select the correct usertype_id, so we can effectively join with other tables. This is more or less equivalent to 1) but without needing to cache the whole usertype table in the php object:
$query = "SELECT info, foo FROM user JOIN usertype USING (usertype_id) WHERE usertype.name = 'admin' ";
3) Define constants that match the keys in the usertype table:
// As defines
define("USERTYPE_ADMIN",1);
define("USERTYPE_REGUSER",2);
//Or as class constants
const USERTYPE_ADMIN = 1;
const USERTYPE_REGUSER = 2;
And then do a simple select.
$query = "SELECT info, foo FROM user WHERE usertype_id = " . USERTYPE_ADMIN;
This is probably the most resource-efficient solution, but it is bad to maintain, as you have to update both the table and the code if you need to modify something in the usertype table..
4) Scrap the usertype table and only keep the types in the php code. I don't really like this because it lets any value get into the database and get assigned to the type of user. But maybe, all things considered, it isn't so bad and i'm just complicating something that should be simple..
Anyways, to sum it up the solution I like most is #2 because it's coherent and with an index on usertype.name, it can't be that bad. But what I've often ended up using is #3, for efficiency.
How would you do it? Any better solutions?
(edit: fixed query in #2)
I would suggest #3 to avoid useless queries, and prevent risk of behavior changes if existing DB table rows are incidentally modified:
Adding the necessary constants in the model class:
class Role // + use namespaces if possible
{
// A good ORM could be able to generate it (see #wimvds answer)
const ADMIN = 1;
const USER = 2;
const GUEST = 3;
//...
}
Then querying like this makes sense:
$query = "SELECT info, foo FROM user WHERE role_id = ".Role::ADMIN;
With an ORM (e.g. Propel in the example below) you'll end up doing:
$isAdminResults = UserQuery::create()->filterByRoleId(Role::ADMIN);
I almost always go for option 3). You could generate the code needed automatically based on what is available in the DB. The only thing you have to remember then is that you have to run the script to update/rewrite that info when you add another role (but if you're using phing or a similar build tool to deploy your apps, just add a build rule for it to your deploy script and it will always be run whenever you deploy your code :p).
Why not denormalize the DB table so instead of having usertype_id, you'd have usertype with the string type (admin). Then in PHP you can just do define('USERTYPE_ADMIN', 'admin');. It saves you from having to modify two places if you want to add a user type...
And if you're really worried about any value getting in, you could always make the column an ENUM data type, so it would self manage...
For tables that will contain "type" values especially when is expected such table to change over time I tend to use simple approach:
Add Varchar column named hid (comes from "human readable id") with unique key. Then I fill it with id meaningful to humans like:
usertype_id (primary key) | name | description | hid (unique key)
---------------------------+------------+-------------------+---------------
1 | 'admin' | 'Administrator' | 'admin'
2 | 'reguser' | 'Registered user' | 'user'
3 | 'guest' | 'Guest' | 'guest'
When you need the actual id you will have to do select based on hid column, i.e.
select usertype_id from tablename where hid = "admin"
This is not an efficient approach but it will ensure compatibility of your application among different deployments (i.e. one client may have 1.admin, 2. guest; other client 1.admin, 2. user, etc.). For your case I think #3 is pretty suitable but if you expect to have more than 10 different user roles - try the "hid" approach.
Are you using any kind of framework here? Could these values be stored in a single source - a config file - which both creates a list of the objects in PHP and also populates the table when you bootstrap the database? I'm thinking from a Rails perspective, as it's been a while since I've written any PHP. Solution there would probably be fixtures.
Why not to make it just
foreach (getdbarr("SELECT * FROM usertype") as $row) {
define($row['name'],$row['id']);
}
You shouldn't need a JOIN in every query to fetch the information about types/roles. You can keep your 'user' model and 'role' models separate in the data access objects (DAO) -- especially since there are so few records for user types.
In most cases where I have a limited number of options that I'd otherwise be joining against a large table, I cache them in memcached as an associative array. In the event I need some information about a particular relationship (like a role) I just lazy load it.
$user = DAO_User::get(1); // this pulls a JOIN-less record
$role = $user->getRole(); // lazy-load
The code for $user->getRole() can be something like:
public function getRole() {
// This comes from a cache that may be called multiple
// times per request with no penalty (i.e. store in a registry)
$roles = DAO_UserRoles::getAll();
if(isset($roles[$this->role_id]))
return $roles[$this->role_id];
return null; // or: new Model_UserRole();
}
This also works if you want to display a list with 1000 users on it. You can simply render values for that column from a single $roles associative array.
This is a major performance improvement on the SQL end, and it goes a long way to reducing complexity in your code base. If you have several other foreign keys on the user table you can still use this approach to grab the necessary information when you need it. It also means you can have dependable Model_* classes without having to create hybrids for every possible combination of tables you might JOIN -- which is much better than simply getting a result set, iterating it, and freeing it.
Even with more than 100 rows on both sides of your JOIN, you can still use the lazy load approach for infrequent or highly redundant information. With a reasonable caching service in your code, there's no penalty for calling DAO_UserRole::get(1500) multiple times because subsequent calls during the same request shouldn't hit the database twice. In most cases you're only going to be displaying 10-25 rows per page out of 1000s, and lazy loading will save your database engine from having to JOIN all the extraneous rows before you actually need them.
The main reason to do a JOIN is if your WHERE logic requires it, or if you need to ORDER BY data from a foreign key. Treating JOINs as prohibitively expensive is a good habit to be in.
For basicly static lookup tables, I generally make static constant files (such as your #3). I generally use classes such as:
namespace Constants;
class UserTypes {
const ADMIN = 1;
const USER = 2;
const GUEST = 3;
}
$id = Constants\UserTypes::ADMIN;
When I'm using lookup takes that are a bit more variable, then I'll pull it into a object and then cache it for 24 hours. That way it only gets updated once a day. That will save you from making database round trips, but allow you to deal with things in code easily.
Yeah, you're right about avoiding #3 and sticking with #2. As much as possible, look-ups like when you use a usertype table to contain the roles and then relate them to the user table using the id values should stay in the database. If you use constants, then the data must always rely on your php code to be interpreted. Also, you can enforce data integrity by using foreign keys (where servers allow) and it will allow you to port the reporting from your php code to other reporting tools. Maintenance also becomes easier. Database administrators won't need to know php in order to derive the meanings of the numbers if you used #3, should they ever be asked to aid in reports development. It may not seem too relevant, but in terms of maintenance, using stored procedures than embedded sql in your php code would also be maintenance-friendly in several ways, and will also be advantageous to DBAs.
I'd go for option #2 and use the join as it is intended to be used. You never know what the future will throw up, it's always better to be prepared today!
With regards to leaving the database alone as much as possible for such operations, there is also the possibility of caching in the long term. For this route, within PHP an option is to use a file cache, one that will only get updated when time calls for it. For the framework I have created, here's an example; I'd be interested to know what people think:
Note:
(LStore, LFetch, GetFileName) belong to a Cache object which gets called statically.
(Blobify and Unblobify) belong to a SystemComponent object which is always alive
Each piece of cache data has a key. this is the only thing you ever have to remember
public function LStore($key,$data, $blnBlobify=true) {
/* Opening the file in read/write mode */
$h = fopen(self::GetFileName($key, 'longstore'),'a+');
if (!$h) throw new Exception('Could not write to cache');
flock($h,LOCK_EX); // exclusive lock, will get released when the file is closed
fseek($h,0); // go to the start of the file
/* truncate the file */
ftruncate($h,0);
if($blnBlobify==true) { $data = SystemComponent::Blobify(array($data)); }
If (fwrite($h,$data)===false) {
throw new Exception('Could not write to cache');
}
fclose($h);
}
public function LFetch($key) {
$filename = self::GetFileName($key, 'longstore');
if (!file_exists($filename)){ return false;}
$h = fopen($filename,'r');
if (!$h){ return false;}
/* Getting a shared lock */
flock($h,LOCK_SH);
$data = file_get_contents($filename);
fclose($h);
$data = SystemComponent::Unblobify($data);
if (!$data) {
/* If unserializing somehow didn't work out, we'll delete the file */
unlink($filename);
return false;
}
return $data;
}
/* This function is necessary as the framework scales different directories */
private function GetFileName($key, $strCacheDirectory='') {
if(!empty($strCacheDirectory)){
return SystemComponent::GetCacheAdd() . $strCacheDirectory.'/' . md5($key);
} else {
return SystemComponent::GetCacheAdd() . md5($key);
}
}
public function Blobify($Source){
if(is_array($Source)) { $Source = serialize($Source); }
$strSerialized = base64_encode($Source);
return $strSerialized;
}
public function Unblobify($strSerialized){
$Decoded = base64_decode($strSerialized);
if(self::CheckSerialized($Decoded)) { $Decoded = unserialize($Decoded); }
return $Decoded;
}
function CheckSerialized($Source){
$Data = #unserialize($Source);
if ($Source === 'b:0;' || $Data !== false) {
return true;
} else {
return false;
}
}
Now when it comes to accessing the actual data, I just call a fetch. For making sure it is up to date, I tell it to store. In your case, this would be after updating the usertype table.

Categories