I'm kind of proud not to accept concepts if there is no good reason. But I'm in doubt about using active record pattern. Currently I'm using zend but say code Igniter has active record.
I dont use.Because
sql is sql it has own syntax.
you can copy to sql editor and it works (if it is working!)
you dont learn another syntax
you dont need to kill your script to gather if active record is writing sql the way you expected
but active record has
you pretend writing like objective php.
When you need to move another db(oracle>mysql :p), you dont need to change rand function to random, active record can make it for you.
does active record have much more capability that I am missing? Can you give some example cases where active record could be a life saver?
An ActiveRecord is
An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.
This is not what CodeIgniter uses. CI's AR is a basic query object.
The main benefit of an ActiveRecord is it's simplicity. If your application is mainly doing simple CRUD operations and your Table structure matches the ActiveRecord very closely, then it's a good choice. It's easy to abstract CRUD in that case. And you can still add handcrafted SQL to it for certain more complex row manipulations.
class User
{
protected static $dbAdapter;
protected $username;
…
public static function findById($id)
{
$result = self::$dbAdapter->query(
sprintf('SELECT * FROM users WHERE id = %d', $id)
);
if($result) {
return new User($result);
}
}
public function create()
{
try {
return self::$dbAdapter->query(
sprintf(
'INSERT into users …',
$this->username,
…
);
);
} catch …
}
…
}
You'd use that in your application like this:
$john = User::findById(123);
echo $user->username; // John
$jane = new User(array(
'username' => 'Jane'
));
$jane->create();
You definitely don't want to use ActiveRecord if the your rows and the AR don't match closely. AR is an object representing a database row. AR couples the object design to the database design. AR is not an ORM. Trying to put that into it is not practical. If you find you are in need of more juicy Domain Models, you won't be happy with it, because it will ultimately hamper your development due to object-relational impedance mismatch.
Additional readings:
Kore Nordmann: Why Active Record sucks
Bill Karwin: ActiveRecord does not suck
Related
I have a simple DB with multiple tables and relationships, ie:
Article - Category
User - Group
etc...
I have implemented SoftDelete behavior where there is a Active column and if set to 0, it is considered deleted.
My question is simple.
How to i specify in as few places as possible that i only want load Articles that belong to Active categories.
I have specified relationships and default scopes (with Active = 1) condition.
However, when i do findAll(), it returns those Articles that have Active = 1, even if the category it belongs to is Active = 0....
Thank you
Implementation so far:
In base class
public function defaultScope()
{
return array('condition' => 'Active = 1');
}
in model:
'category' => array(self::BELONGS_TO, 'Category', 'CategoryID'),
'query':
$data = Article::model()->findAll();
MY SOLUTION
So i decided, that doing it in framework is:
inneficient
too much work
not good as it moves business logic away from database - this is fairly important to save work later on when working on interfaces/webservices and other customizations that should be part of the product.
Overall lesson: Try to keep all business logic as close to database as possible to prevent disrepancies.
First, i was thinking using triggers that would propagate soft delete down the hierarchy. However after thinking a bit more i decided not to do this. The reason is, that this way if I (or an interface or something) decided to reactivate the parent records, there would be no way to say which child record was chain-deleted and which one was deleted before:
CASE:
Lets say Category and Article.
First, one article is deleted.
Then the whole category is deleted.
Then you realize this was a mistake and you want to undelete the Category. How do you know which article was deleted by deleting category and which one should stay deleted? Yes there are solutions, ie timestamps but ...... too complex, too easy to break
So my solution in the end are:
VIEWS. I think i will move away from yii ORM to using views for anything more complex then basic things.
There are two advantages to this for me:
1) as a DBA i can do better SQL faster
2) logic stays in database, in case the application changes/another one is added, there is no need to implement the logic in more then one places
You need to specify condition when you are using findAll method. So You should use CDbCriteria for this purpose:
$criteria=new CDbCriteria;
$criteria->with = "category";
$criteria->condition = "category.Active = 1"; //OR $criteria->compare('category.active', 1 true);
$data = Article::model()->findAll($criteria);
You should also have a defaultScope in your Article model, condition there should add category.Active = 1 or whatever your relation is named.
public function defaultScope()
{
return array('condition' => 't.Active = 1 AND category.Active = 1');
}
I don't remember by now but it might be you have to specify the relation:
return array(
'with' => array("category" => array(
'condition'=> "t.Active = 1 AND category.Active = 1",
)
);
I may no be asking this questions right but here goes... I have a database with 2 tables "users" (for users name/password/etc) and "usersInfo" (users first name/last/address/etc). I only have 1 Users.php class- do i need 2 separate classes to create 2 objects from to hold the "users" & "usersInfo" data for the same user or will 1 class work (and still make 2 objects?)?
some of my Users.php class/
public function __construct($user = null) {
$this->_db = DB::getInstance();
$this->_sessionName = Config::get('session/session_name');
$this->_cookieName = Config::get('remember/cookie_name');
if(!$user) {
if(Session::exists($this->_sessionName)) {
$user = Session::get($this->_sessionName);
if($this->find($user) || $this->findUserInfo($user)) {
$this->_isLoggedIn = true;
} else {
//logout
}
}
} else {
$this->find($user);
$this->findUserInfo($user);
}
}
public function find($user = null) {
if($user) {
$field = (is_numeric($user)) ? 'id' : 'username';
$data = $this->_db->get('users', array($field, '=', $user));
if($data->count()) {
$this->_data = $data->first();
return true;
}
}
return false;
}
public function findUserInfo($user = null) {
if($user) {
$test3 = $this->_db->get('users', array('username', '=', $user));
$userId = $test3->first()->id;
$data2 = $this->_db->get('usersInfo', array('user_id', '=', $userId));
if($data2->count()) {
$this->_userInfoData = $data2->first();
return true;
}
}
return false;
}
public function data() {
return $this->_data;
}
public function userInfoData() {
return $this->_userInfoData;
}
Currently I have to create 2 objects to use all the data i need for the same user.
for example, in on of my pages.php i have:
$user = new User();
$user1 = new User($user->data()->username);
$userNane = $user->data()->username; //holds users username form "users" table
$userName1 = $user1->userInfoData()->first_name; // holds users first name from "usersInfo" table
It works but doesnt look right... is it efficient/ok practice/etc. If not, suggestions?
Also, first post, take it easy :)
What is the relationship between the 2 tables?
1-1 ? 1-*? Can a user have multiple persona? Can a persona correspond to many user accounts? Will this relation change in the future?
Depending on your answers, you might see which solution fit better your plans.
1-1 relation: you can afford to have a single class to hold related records. It will be easier to manage from the perspective of your application
Otherwise, you'll need at one time or another to handle a record separately from related records in the other table. You'll be better off with 2 distincts objects.
if you plan to change things later on for the second situation, you should keep things as they are.
In the specific case of user data, your comment bring the insight that certain data are more sensitive than others. In retrospect, I guess that's the reason you made these two tables separate. From that point of view, it is certainly better to keep both objects separate, even in a 1-1 relationship.
Regarding your code, indeed, having a dedicated UserInfo class, rather than piggy backing on another instance of User, would clearly be a good thing. A very important idea of good design is separation of concerns: you want each class to handle one and only one purpose, so that any modification to a class will have a limited scope of impact on the rest of the code.
As an example, the only thing you need to retreive a userinfo row, and therefore construct an object wrapping it, is the user id. Instead of delegating the whole job to a method of User, I would probably extract the iout of the User instance, and pass it to the adhoc UserInfo constructor or static method: there, each class only deals with things in its own perimeter. Of course, findUserInfo could also delegate to that same function.
IMO, on of the most important steps in designing/developing an app is creating a sound schema and model. Not sure how much database design experience you have, but you will want to read up on First Normal Form (1NF), and eventually (2NF and 3NF).
Part of the schema design stage is to identify all the nouns which you will reference in your app, in your case a user is a perfect example. Each of these identified nouns will then have attributes, which you will want to consider, in how each will be stored.
The problem in your situation is that you have user and user_info. As you stated user is for name, password, etc, whereas user_info is for first_name, last_name, address etc. Part of the design stage is to determine which of these attributes are directly attributable to the user object, and which are more ancillary in nature. Using your example: name, password, first_name, last_name are each directly attributable to the user noun (object), however address is more ancillary in nature, and there may be more than one address per user (billing address, vs physical address), so you may want to consider adding a user_address table. As you can see, by logically separating the attributes of the user noun (object), you start to identify relationships which make more sense (user, user_address) vs (user, user_info).
Once you identify the nouns, and separate their attributes, you can create your schema. From your schema you can use an Object Relational Mapper (ORM) like Doctrine, which will introspect your schema, and generate objects for you to use throughout your app. In your example you would end up with two objects; User and UserAddress. Also it's important that when developing your schema that you identify relationships between tables by implementing a foreign key constraints. For example, your user_address table should have a user_id column, which links to your user table. This way when doctrine introspects your schema, it will also identify these relationships, which makes coding much easier.
Once you have your ORM in place, you can then make code references like this:
// In your controller
$this->user = UserTable::findById($_SESSION['user_id']);
// Then in your view
Welcome <?php echo $user->getFirstName() ?>, to our wonderful app.
We have your addresses listed as follows:
<?php foreach ($user->getUserAddress() as $userAddress) ?>
<div>
<?php echo $address->getStreet() ?>
</div>
<?php endforeach ?>
Yes, it's a very simplistic example, but should properly demonstrate that if you design your schema properly, the code becomes semantic, which makes it easier to write, and maintain.
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.
Here is situation.... ...
I have a DBManager, which is implement a DBInterface, in the DBInterface, I got 4 method:
-create(DBCmd);
-read(DBCmd);
-update(DBCmd);
-delete(DBCmd);
The DBCmd object is responsible for generate the SQL statement, and the DBCmd requires an object in sql statement:
class DBCmd{
public _constructor($aObj){
}
public executeCreate(){
}
public executeRead(){
}
public executeUpdate(){
}
public executeDelete(){
}
}
The flow will be like this:
aObject ---> put it into DBCmd ----> put the DBCmd in DBManager ---> execute
But the problems happen when I get some objects related to other tables, for example...a customer have a purchase record, and which purchase record have many items....
So, what do I do in my read method? should I read all the records related to the customer?? Do I need to loop all the items inside the purchase record too?
If yes, when I doing read customer, I need to query 3 tables, but some that may not need to see.....it waste the resource...
And I come up with another solution, I make a new set of DBCmd, that allow me to get the related DB items, for example:
class getReleatedPurchaseRecordDBCmd{
public _constructor($aCustomerObject){
}
//.... ....
}
But in this "solution", I got some problems, is I loss the relationship in the object customer...yes, I can read back all the records, get the customer object basically don't know any things about the purchase record....
Some may ask me to do something like this:
class customer{
//skip other methods...
public getPurchaseRecords(){
//query the db
}
}
It works, but I don't want the object structure have some strong relationship between the db....That's why I come up with the DBCmd stuff...
So, everything seems to be very coupling, how can solve it? Thank you.
for stuff like this i tend to get the count of sub objects with the initial query usually involving sql COUNT and JOIN, then have a seperate getSubObjects command that can be called if needed later. So for example:
$datamodel->getCustomer($id);//or some such method
returns
class Customer{
$id = 4;
$recordCount = 5;
$records = null;
}
I can then use the count for any display stuff as needed, and if i need the records populated call:
$customer->records = $datamodel->getCustomerRecords($customer->id);
So I'm trying to adopt good object oriented programming techniques with PHP. Most (read all) of my projects involve a MySQL database. My immediate problem deals with the users model I need to develop.
My current project has Agents and Leads. Both Agents and Leads are Users with much of the same information. So, obviously, I want a class Agents and a class Leads to extend a common class Users. Now, my question is as follows:
How should the SQL best be handled for loading these objects? I don't want to execute multiple SQL statements when I instantiate an Agent or a Lead. However, logic tells me that when the Users constructor is fired, it should execute a SQL statement to load the common information between Agents and Leads (username, password, email, contact information, etc). Logic also tells me that when the Agents or Leads constructor is fired, I want to execute SQL to load the data unique to the Agents or Leads class....But, again, logic also tells me that it's a bad idea to execute 2 SQL statements every time I need an Agent or Lead (as there may be thousands of each).
I've tried searching for examples of how this is generally handled with no success...Perhaps I'm just searching for the wrong thing?
You basically have three approaches to this problem (one of which I'll eliminate immediately):
One table per class (this is the one I'll eliminate);
A record type with optional columns; and
A record type with a child table depending on type that you join to.
For simplicity I generally recommend (2). So once you have your table:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
type VARCHAR(10),
name VARCHAR(100)
);
where type can be 'AGENT' or 'LEAD' (for example). Alternatively you can use one character type codes. You can then start to fill in the blanks with the object model:
You have a User parent class;
You have two child classes: Lead and Agent;
Those children have a fixed type.
and it should fall into place quite easily.
As for how to load in one statement, I would use some kind of factory. Assuming these barebones classes:
class User {
private $name;
private $type;
protected __construct($query) {
$this->type = $query['type'];
$this->name = $query['name'];
}
...
}
class Agent {
private $agency;
public __construct($query) {
parent::constructor($query);
$this->agency = $query['agency'];
}
...
}
class Lead {
public __consruct($query) {
parent::constructor($query);
}
...
}
a factory could look like this:
public function loadUserById($id) {
$id = mysql_real_escape_string($id); // just in case
$sql = "SELECT * FROM user WHERE id = $id";
$query = mysql_query($sql);
if (!query) {
die("Error executing $sql - " . mysql_error());
}
if ($query['type'] == 'AGENT') {
return new Agent($query);
} else if ($query['type'] == 'LEAD') {
return new Lead($query);
} else {
die("Unknown user type '$query[type]'");
}
}
Alternatively, you could have the factory method be a static method on, say, the User class and/or use a lookup table for the types to classes.
Perhaps polluting the classes with the query result resource like that is a questionable design in the strictest OO sense, but it's simple and it works.
Will you ever have a user that's not a Lead or Agent? Does that class really need to pull data from the database at all?
If it does, why not pull the SQL query into a function you can override when you create the child class.
Could you not inherit say a skeleton of the SQL, then use a function in each sub-class to complete the query based on its needs?
Using a really basic example:
<?php
//our query which could be defined in superclass
$query = "SELECT :field FROM :table WHERE :condition";
//in our subclass
$field = "user, password, email";
$table = "agent";
$condition = "name = 'jim'";
$dbh->prepare($query);
$sth->bindParam(':field', $field);
$sth->bindParam....;//etc
$sth->execute();
?>
As you can see my example isn't amazing, but should allow you to see what I am getting at. If your query is very similar between subclasses then I think my suggestion could work.
Obviously it will need some tweaking but it is probably the approach I would take.