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.
Related
What I am trying to do
I want to query a specific set of records using active model like so
$jobModel = Jobs::find()->select('JOB_CODE')->distinct()->where(['DEPT_ID'=>$dept_id])->all();
Then I want to assign a flag attribute to the records in this activerecord based on whether they appear in a relationship table
What I have tried
So in my job model, I have declared a new attribute inAccount. Then I added this function in the job model that sets the inAccount flag to -1 or 0 based on whether a record is found in the relationship table with the specified account_id
public function assignInAccount($account_id){
if(JobCodeAccounts::find()->where(['JOB_CODE'=>$this->JOB_CODE])->andWhere(['ACCOUNT_ID'=>$account_id])->one() == null){
$this->inAccount=0;
}
else{
$this->inAccount = -1;
}
}
What I have been doing is assigning each value individually using foreach like so
foreach($jobModel as $job){
$job->assignInAccount($account_id);
}
However, this is obviously very slow because if I have a large number of records in $jobModel, and each one makes a db query in assignInAccount() this could obviously take some time if the db is slow.
What I am looking for
I am wondering if there is a more efficient way to do this, so that I can assign inAccount to all job records at once. I considered using afterFind() but I don't think this would work as I need to specify a specific parameter. I am wondering if there is a way I can pass in an entire model (or at least array of models/model-attributes and then do all the assignations running only a single query.
I should mention that I do need to end up with the original $jobModel activerecord as well
Thanks to scaisEdge's answer I was able to come up with an alternative solution, first finding the array of jobs that need to be flagged like so:
$inAccountJobs = array_column(Yii::$app->db->createCommand('Select * from job_code_accounts where ACCOUNT_ID = :account_id')
->bindValues([':account_id' => $account_id])->queryAll(), 'JOB_CODE');
and then checking each job record to see if it appears in this array like so
foreach($jobModel as $job){
if(in_array($job->JOB_CODE, $inAccountJobs))
$job->inAccount = -1;
else
$job->inAccount = 0;
}
Does seem to be noticeably faster as it requires only a single query.
Suppose I have an Employees data.
Each Employee has its employee type, e.g.: Part-timer, Internee, Manager.
Those types are fixed and used throughout the entire application, and its purpose is read-only.
The question is, should I store those employee types into database, like this :
table employee_type
ID | Name
--------------------
1 | Part-timer
2 | Internee
3 | Manager
or inside the code, as a static constant variables (or enums), like this in PHP code :
class Employee
{
const EMPLOYEE_TYPE_PARTTIMER = 1;
const EMPLOYEE_TYPE_INTERNEE = 2;
const EMPLOYEE_TYPE_MANAGER = 3;
...
...
}
The advantage of using the code is, you can use those employee types like this
$employee = new Employee( Employee::EMPLOYEE_TYPE_MANAGER );
...
...
if ( $employee->getType() == Employee::EMPLOYEE_TYPE_PARTTIMER )
{ ... }
So, which one is better ?
Or maybe there is a better solution than both of them ?
If you have (or may have) other tables in your database in which you store the ID, I'd store the definition in a table to enable integrity constraints for the children rows in other tables. If you want to, you could also use the constants, but this redundancy is a possible source of mistakes because you could end with different values in source and in database
I would suggest you make it into the database. and make a panel/form when you want to change the status of an employee. It will make the application robust and more dynamic.
But like you said in here
"Those types are fixed and used throughout the entire application, and its purpose is read-only."
Static data = Constant Value
Dynamic data = Database
Still i prefer the database.
Using CakePHP 2.2, I am building an application in which each client has it's own "realm" of data and none of the other data is visible to them. For example, a client has his set of users, courses, contractors and jobs. Groups are shared among clients, but they cannot perform actions on groups. All clients can do with groups is assign them to users. So, an administrator (using ACL) can only manage data from the same client id.
All my objects (except groups, of course) have the client_id key.
Now, I know one way to get this done and actually having it working well, but it seems a bit dirty and I'm wondering if there is a better way. Being early in the project and new to CakePHP, I'm eager to get it right.
This is how I'm doing it now :
1- A user logs in. His client_id is written to session according to the data from the user's table.
$user = $this->User->read(null, $this->Auth->user('id'));
$this->Session->write('User.client_id', $user['User']['client_id']);
2- In AppController, I have a protected function that compares that session id to a given parameter.
protected function clientCheck($client_id) {
if ($this->Session->read('User.client_id') == $client_id) {
return true;
} else {
$this->Session->setFlash(__('Invalid object or view.'));
$this->redirect(array('controller' => 'user', 'action' => 'home'));
}
}
3- Im my different index actions (each index, each relevant controller), I check the client_id using a paginate condition.
public function index() {
$this->User->recursive = 0;
$this->paginate = array(
'conditions' => array('User.client_id' => $this->Session->read('User.client_id'))
);
$this->set('users', $this->paginate());
}
4- In other actions, I check the client_id before checking the HTTP request type this way.
$user = $this->User->read(null, $id);
$this->clientCheck($user['User']['client_id']);
$this->set('user', $user);
The concept is good - it's not 'dirty', and it's pretty much exactly the same as how I've handled situations like that.
You've just got a couple of lines of redundant code. First:
$this->Auth->user('id')
That method can actually get any field for the logged in user, so you can do:
$this->Auth->user('client_id')
So your two lines:
$user = $this->User->read(null, $this->Auth->user('id'));
$this->Session->write('User.client_id', $user['User']['client_id']);
Aren't needed. You don't need to re-read the User, or write anything to the session - just grab the client_id directly from Auth any time you need it.
In fact, if you read http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#accessing-the-logged-in-user it even says you can get it from outside the context of a controller, using the static method like:
AuthComponent::user('client_id')
Though it doesn't seem you'll be needing that.
You could also apply the client_id condition to all finds for a Model by placing something in the beforeFind function in the Model.
For example, in your User model, you could do something like this:
function beforeFind( $queryData ) {
// Automatically filter all finds by client_id of logged in user
$queryData['conditions'][$this->alias . '.client_id'] = AuthComponent::user('client_id');
return $queryData;
}
Not sure if AuthComponent::user('client_id') works in the Model, but you get the idea. This will automatically apply this condition to every find in the model.
You could also use the beforeSave in the model to automatically set that client_id for you in new records.
My answer may be database engine specific as I use PostgreSQL. In my project I used different schema for every client in mysql terms that would be separate database for every client.
In public schema (common database) I store all data that needs to be shared between all clients (objects that do not have client_id in your case), for example, variable constants, profile settings and so on.
In company specific models I define
public $useDbConfig = 'company_data';
In Controller/AppController.php beforeFilter() method I have this code to set schema according to the logged in user.
if ($this->Session->check('User.Company.id')) {
App::uses('ConnectionManager', 'Model');
$dataSource = ConnectionManager::getDataSource('company_data');
$dataSource->config['schema'] =
'company_'.$this->Session->read('User.Company.id');
}
As you see I update dataSource on the fly according to used company. This does exclude any involvement of company_id in any query as only company relevant data is stored in that schema (database). Also this adds ability to scale the project.
Downside of this approach is that it creates pain in the ass to synchronize all database structures on structure change, but it can be done using exporting data, dropping all databases, recreating them with new layout and importing data back again. Just need to be sure to export data with full inserts including column names.
i'm trying to do a complex query with Criteria in a Symfony project using Propel ORM.
the query i want to make is, in human words:
Select from the 'interface' table the registers that:
- 1 are associated with a process (with a link table)
- 2 have a name similat to $name
- 3 its destiny application's name is $apd (application accecible by foreign key)
- 4 its originapplication's name is $apo (application accecible by foreign key)
here the code i made, and not working:
$c = new Criteria();
$c->addJoin($linkPeer::CODIGO_INTERFASE,$intPeer::CODIGO_INTERFASE); //1
$c->add($linkPeer::CODIGO_PROCESONEGOCIO,$this->getCodigoProcesonegocio());//1
if($name){
$name = '%'.$name.'%'; //2
$c->add($intPeer::NOMBRE_INTERFASE,$name,Criteria::LIKE); //2
}
if($apd){
$apd = '%'.$apd.'%'; //3
$c->addJoin($appPeer::CODIGO_APLICACION,$intPeer::CODIGO_APLICACION_DESTINO);//3
$c->add($appPeer::NOMBRE_APLICACION,$apd,Criteria::LIKE); //3
}
if($apo){
$apo = '%'.$apo.'%';//4
$c->addJoin($appPeer::CODIGO_APLICACION,$intPeer::CODIGO_APLICACION_ORIGEN);//4
$c->add($appPeer::NOMBRE_APLICACION,$apo,Criteria::LIKE);//4
}
After that i did a $c->toString() to see the SQL generated and i saw that when i send only an $apd value, the SQL is correct, when i send an $apo value too. But when i send both, only the $apo AND apears on the SQL.
I guess its because the $c->add(...) call is the same with a distinct parameter, but not sure at all. Is this the error? What is the best way to generate my query correctly?
Thank you very much for your time! :D
Yes, it's overriding the previous call as the Criteria object only stores one condition per field. The solution is to create 2 or more separate Criterion objects and mix them into the Criteria object:
//something like this
$cron1 = $criteria->getNewCriterion();
$cron1->add($appPeer::NOMBRE_APLICACION,$apo,Criteria::LIKE);//4
$criteria->add($cron);
//and the same with the other criterion
However, it would be much easier to upgrade to Propel15+, where you work on the Query class level, and multiple restrictions on the same field don't override each other.
Hope this helps,
Daniel
I have a nasty problem. I want to get rid of a certain database field, but I'm not sure in which bits of code it's called. Is there a way to find out where this field is used/called from (except for text searching the code; this is fairly useless seeing as how the field is named 'email')?
Cheers
I would first text search the files for the table name, then only search the tables that contain the table name for the field name.
I wrote a program to do this for my own purposes. It builds an in-memory listing of tables and fields and relates the tables to the fields. Then it loops through tables, searching for the code files that contain the table names, and then searches those files for the fields in the tables found. I'd recommend a similar methodology in your case.
setting mysql to log all queries for some time might help. the queries will give you the tip where to look
brute force - set up a test instance - remove the column - and excercise your test suite.
create a before insert trigger on that table that monitors the insertion on that column.
at the same time create another table called monitor with only one column email
make that table insert the value of NEW.email field into monitor.email as well as in real table.
so you can run your application and check for the existence of any non-null value in monitor table
You should do this in PHP i would expect
For example:
<?php
class Query
{
var $command;
var $resource;
function __construct($sql_command = '')
{
$this->command = $sql_command;
}
public function setResource($resource)
{
$this->resource = $resource;
}
}
//then you would have some kind of database class, but here we would modify the query method.
class Database
{
function query(Query $query)
{
$resource = mysql_query($query->command);
$query->setResource($resource);
//Then you can send the class to the monitor
QueryMonitor::Monitor($query);
}
}
abstract class QueryMonitor
{
public static Monitor(Query $query)
{
//here you use $query->resource to do monitoring of queryies
//You can also parse the query and gather what query type it was:-
//Select or Delete, you can also mark what tables were in the Query
//Even meta data so
$total_found = mysql_num_rows($query->resource);
$field_table = mysql_field_table ($query->resource);
//Just an example..
}
}
?>
Obviously it would be more advanced than that but you can set up a system to monitor every query and every queries meta data in a log file or w.e