I am using CI, however this question applies to models and db persistence in general. I find myself creating methods in models like:-
private function create_joins() {
# Add some joins to the global db object using the active record
}
I do this so I can then perform common joins for a particular model without duplicating the code for creating the joins.
So a select method might be:-
public function get_by_id($id) {
$this->db->select('some_cols');
$this->db->from('some_table');
$this->create_joins();
$this->db->where(array('id' => $id));
etc...
}
Which is fine, but I am wondering if this is the sort of thing that an ORM like datamapper can abstract away?
You should try Doctrine, which is one of the most advanced ORM in PHP:
Using Doctrine, you won't even have to write methods such as get_by_id($id) in your model : they are handled by Doctrine itself.
So you would be able to write:
$entityManager->find("Bug", $id);
An alternative is to use php-activerecord via sparks
An example of Associations
-
class User extends ActiveRecord\Model{
//set tablename
//I would advice to keep Model singular and table names plural
//then this next step is not needed
static $table_name = 'table_one';
//set relationships
static $belongs_to array(
array('Group')
);
static $has_many = array(
array('Comment'),
array('Order')
);
static $has_one = array(
array('Additional_Info')
);
//one key feature is callbacks
//this helps keep controllers clean by letting you pass
//the post data(after validation) in to modify(serialize,json_encode,calculate vat etc)
static $before_create = array('json_encode_my_tags');
public function json_encode_my_tags(){
//$this->tags === $this->input->post('tags');
$tags = explode(',', str_replace(' ', '', $this->tags));
return $this->tags = json_encode($tags, TRUE);
}
}
//calling a model and using eager-loading for associations
$model = User::find(array(
'conditions' => array('id=?', $id) // PK Primary key
'include' => array('comment', 'order', 'additional_info') // eager-loading associations
));
$model->key;
$model->comment->key;
$model->additional_info->key;
$model->order->key;
Related
I have a DataObject called Applicant and it $has_one Member (this is the SilverStripe Member class).
private static $has_one = array (
'MemberApplicant' => 'Member'
);
When a member is logged in and visits the ApplicationPage I want to be able to populate the form based on the members Applicant data.
I can make this work but I feel like I should be able to access the data easier.
Here is what I do:
$memberID = Member::currentUserID();
$applicant = Applicant::get()->filter('MemberApplicantID', $memberID)->first();
$form->loadDataFrom($applicant);
Shouldn't I be able to instantiate a Member and then call its relative $MemberApplicant?
Shouldn't I be able to instantiate a Member and then call its relative $MemberApplicant?
Of course. I assume you have a 1:1 relation, then you have to define the counter part on Member using $belongs_to (see this diagram)
class Applicant extends DataObject
{
private static $has_one = [
'MemberApplicant' => 'Member'
];
...
class MemberApplicantExtenension extends DataExtension
{
private static $belongs_to = [
'Applicant' => 'Applicant'
];
...
Now add the DataExtension to the Member object in /mysite/_config/config.yml
Member:
extensions:
- MemberApplicantExtenension
and run a dev/build?flush.
Now you're able to get the related Applicant from the Member using built in ORM magic:
//Get the current Member
$member = Member::CurrentUser();
//get the $belongs_to
$applicant = $member->Applicant();
It sounds like you want to be able to avoid an "additional" ORM query by going through the current user's Member record, however this is not how SilverStripe (Or any system that uses SQL JOINs works).
When you add a $has_one to a class, you essentially add a foreign key to that class's table. In your case it will be called MemberApplicantID, which is a foreign key to the Member table's primary key - its ID field. Thus your ORM query needs to go through an Applicant instance first.
Caveat: The above is not entirely true, DataObject does allow you to define a private (SilverStripe config) static $belongs_to on your model classes which lets you query "The other way around". I've never actually done this before, but it does look like you'd declare this on a custom DataExtension that decorates Member and has a value of "Applicant". See DataObject::belongsToComponent().
You can also simplify your existing ORM query slightly without having to instantiate a DataList explicitly, but the route to your target data remains the same:
// SilverStripe >= 3.2
$applicant = DataObject::get_one('Applicant', array('MemberApplicantID = ?' => $memberID));
// SilverStripe < 3.2
$applicant = DataObject::get_one('Applicant', '"MemberApplicantID" = ' . $memberID);
How can customised my query.. this is my current code in my controller:
class PostController extends AbstractActionController
{
private $userTable;
// CRUD
// retrieve
public function indexAction(){
return new ViewModel(
array(
'rowset' => $this->getPostsTable()->select(),
)
);
}
public function getPostsTable(){
if(!$this->userTable){
$this->userTable = new TableGateway(
'posts',
$this->getServiceLocator()->get('Zend\Db\Adapter\Adapter')
);
}
return $this->userTable;
}
}
How can i order the result to descending?
And how to join another table with that code?
First of all, Zend framework is an MVC Framework.
Means that your Data Object Access MUST be in Model layer NOT IN Controller.
Your PostController can't have Model logic in it, it's bad. And it may be throw so much error that you will not understand directly.
Plus, call getServiceLocator in Controller is a bad practise and it will be removes in Zf3. That's why using Model Layer is recommanded.
For your problem, you have to make a query builder like this :
$sql = new \Zend\Db\Sql\Sql($this->getAdapter());
$select = $sql->select()
->from('tableName')
->columns(array())
->join('tableName2', 'Your ON Clause')
->where(array('if you Have WhereClause'))
->order('Your column DESC');
I use Doctrine but i'm pretty sure (community will confirm this or not) this example may work.
I just started cakephp following there tutorials
I'm able to grab the posts table in my post controller and spew it onto my index.ctp
In my view for the post controller i also want to list the User name that posted the article. My post table has a user_id, so i need to match it to my user table and pass it along
class PostsController extends AppController {
public function index() {
//passes values to the view
$this->set('posts', $this->Post->find('all'));
//is "Post" a post method? or is it the name of the table? i'm unsure of the syntax
$this->set('users', $this->Users->find('all')); //this does not work
}
}
thank you for your help with this basic question
You must use 'recursive'
$this->Post->find('all', array(
'recursive' => 2,
// ...
));
Of course, you first need to link models together
I assume that you have already set a belongsTo association (Post belongsTo User) and/or a hasMany association (User hasMany Post). If so, cake will automaticly brings the associated models (unless you put $recursive = -1 on your model).
Thus you'll have access to the users related to each post on the view: posts[i]['User']
You can also use this on your view to see the view variables:
debug($this->viewVars)
put this on your Post model if you don't:
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
)
);
Make sure that you load models corretly (in case you want to load the User model inside PostsController).
So simply add this attribute inside your class controller.
public $uses = array('Post','User');
to link models together . u need to add the association inside your Post model .
public $belongsTo = array(
'User'=>array(
'className'=> 'User',
'foreignKey'=>'user_id'
)
);
and i you want to retrieve data from database you have to set your recursivity and there is two ways
first one :
$posts = $this->Post->find('all',array('recursive'=>2));
// or
$this->Post->recursive = 2;
$posts = $this->Post->find('all');
second one : use the Containable behavior
set the recursivity to -1 in the AppModel and include the behavior
public $recursive = -1;
public $actsAs = array('Containable');
so simply u can retieve posts with any other linked models like that
$posts = $this->Post->find('all',array(
'contain'=>array('User'),
// ...
)));
In model:
public function getOptionsGender()
{
array(0=>'Any', 1=>Male', 2=>'Female');
}
In view (edit):
echo $form->dropDownList($model, 'gender', $model->optionsGender);
but I have a CDetailView with "raw" attributes, and it displays numbers instead of genders.
$attributes = array(
...
'gender',
)
What is appropriate way to convert these numbers back to genders? Should I do it in a model, replacing fields such as $this->gender = getOptionsGender($this->gender)? Any github examples will be very appreciated.
I had to choose gender, age, city, country etc. in a few views that are not related to this one. Where should I place my getOptionsGender function definitions?
Thank for your help, the problem is solved.
In model:
public function getGenderOptions() { ... }
public function genderText($key)
{
$options = $this->getGenderOptions();
return $options[$key];
}
In view:
$attributes = array(
array (
'name'=>'gender',
'type'=>'raw',
'value'=>$model->genderText($model->gender), //or $this->genderText(...)
),
);
$this->widget('zii.widgets.CDetailView', array(
'data'=>$model,
'attributes'=>$attributes,
));
The working example can be found here:
https://github.com/cdcchen/e23passport/blob/c64f50f9395185001d8dd60285b0798098049720/protected/controllers/UserController.php
In Jeffery Winsett's book "Agile Web Application Development with Yii 1.1", he deals with the issue using class constants in the model you are using. In your case:
class Model extends CActiveRecord
{
const GENDER_ANY=0;
const GENDER_MALE=1;
const GENDER_FEMALE=2;
public function getGenderOptions(){
return array(
self::GENDER_ANY=>'Any',
self::GENDER_MALE=>'Male',
self::GENDER_FEMALE=>'Female',
);
}
public function getGenderText(){
$genderOptions=$this->genderOptions();
return isset($genderOptions[$this->gender]) ? $genderOptions[$this->gender] : "unkown gender({$this->gender})";
}
}
Then in your CDetailView you would have to alter it from gender to:
array(
'name'=>'gender',
'value'=>CHtml::encode($model->genderText()),
),
If several models have the same data, you may want to create a base model that extends CActiveRecord and then extend the new model instead of CActiveRecord. If this model is the only one with that data (ie User model only has gender), but other views use that model to display data, then I would leave it just in the single model class. Also keep in mind that if you place getGenderOptions in the extended class, and you extend ALL your models, they will all have that option available, but may not have the attributes needed and will throw an error if you aren't checking for it.
All this being said, I still think it is a matter or preference. You can handle it however you want, wherever you want. This is just one example from a book I have specifically on Yii.
Is there a method in Doctrine like Hibernate's findByExample method?
thanks
You can use the findBy method, which is inherited and is present in all repositories.
Example:
$criteria = array('name' => 'someValue', 'status' => 'enabled');
$result = $em->getRepository('SomeEntity')->findBy($criteria);
You can create findByExample method in one of your repositories using a definition like this:
class MyRepository extends Doctrine\ORM\EntityRepository {
public function findByExample(MyEntity $entity) {
return $this->findBy($entity->toArray());
}
}
In order for this to work, you will have to create your own base class for the entities, implementing the toArray method.
MyEntity can also be an interface, which your specific entities will have to implement the toArray method again.
To make this available in all your repositories, ensure that you are extending your base repository class - in this example, the MyRepository one.
P.S I assume you are talking about Doctrine 2.x
Yes.
Let's say you have a model called Users. You have the following two classes
abstract class Base_User extends Doctrine_Record
{
//define table, columns, etc
}
class User extends Base_User
{
}
in some other object you can do
$user = new User;
//This will return a Doctrine Collection of all users with first name = Travis
$user->getTable()->findByFirstName("Travis");
//The above code is actually an alias for this function call
$user->getTable()->findBy("first_name", "Travis");
//This will return a Doctrine Record for the user with id = 24
$user->getTable()->find(24);
//This will return a Doctrine Collection for all users with name=Raphael and
//type = developer
$user->getTable()
->findByDql("User.name= ? AND User.type = ?", array("Raphael", "developer"));
$users = $userTable->findByIsAdminAndIsModeratorOrIsSuperAdmin(true, true, true);
See http://www.doctrine-project.org/projects/orm/1.2/docs/manual/dql-doctrine-query-language/en