I'm trying to retrieve information from a database using Kohana ORM.
There are two relevant tables in my database:
branches
id smallint
parent_id smallint
name varchar
active int
branches_options
id mediumint
branche_id smallint
name varchar
customer_id int
With the following code I want to retrieve the information from the branches_options table
` $branchesOptions[] = ORM::factory('branches_option')
->where('branche_id', '=', $subBranche)
->join('branches', 'LEFT')->on('branches.id', '=', 'branches_options.branche_id')
->order_by('name')
->find_all()
->as_array();`
Now I want to see the value of branches.name in the result set, but I'm not sure how to do this in Kohana.
The code of the models is:
`class Model_Branche extends ORM
{
protected $_has_many = array(
"options" => array('model' => 'branches_option'),
"adwords_templates" => array ('model' => 'adwords_template')
);
public $result = array();`
and
`class Model_Branches_option extends ORM
{
protected $_has_many = array (
"keywords" => array('model' => 'branches_options_keyword')
);
protected $_has_and_belongs_to = array (
"adwords_templates" => array (
"model" => "adwords_template",
"through" => "branches_options_templates"
)
);
protected $_belongs_to = array ( "branche" => array () );`
Can this be done and if so, how?
You need to make some important changes to you models for this to work properly:
class Model_Branche extends ORM
{
protected $_has_many = array(
'options' => array(
'model' => 'branches_option',
'foreign_key' => 'branche_id'
)
);
}
And the Branches_Option model (it should be in model/branche/ folder):
class Model_Branches_Option extends ORM
{
protected $_belongs_to = array(
'branche' => array()
);
}
Now you can do something like that:
$options = ORM::factory('branche', $branche_id)
->options
->find_all();
foreach ($options as $option)
{
$branche_active = $option->branche->active;
$branche_name = $option->branch->name;
$option_name = $option->name;
}
One of the most important changes here is that we specify the foreign_key option in the $_has_many relationship. Since the ORM is using the Kohana Inflector helper it might not recognize it automatically (branches in singular form is branch and not branche).
If it doesn't work try specifying the $_table_name property for the same reason.
I'm trying to also grasp the concept here. In order to think like an ORM in this regard, you need to start with tables that the main tables reference as related information.
So in my world, I have a waste report, (model/waste/report) and there exists another table that has all the codes (model/waste/codes). so in the waste_reports table there's a column called code. That field might have 2E. 2E means nothing without the waste_codes table. The waste_codes table is (id, code, name).
I defined this relationship as such:
class Model_Waste_code extends ORM {
protected $_primary_key = "code";
protected $_has_one = array(
'waste_report' => array()
);
}
Then in my waste report model:
class Model_Waste_report extends ORM
{
protected $_belongs_to = array(
'codes' => array(
'model' => 'waste_code',
'foreign_key' => 'code'
)
);
}
To show different examples:
public function action_ormwaste() {
$test = ORM::factory('waste_report',76);
if ($test->loaded())
echo $test->codes->name;
echo "<hr noshade />";
$test = ORM::factory('waste_report')->find_all();
foreach($test as $row)
echo $row->codes->name . "<BR>";
}
Would output:
Error with image file (mirrored, etc)
-------------------------------------
Wrong Size
Extra Prints
Error with image file (mirrored, etc)
etc...
So in essense, the join on the data is handled on the fly. I'm using Kohana 3.2.
Thanks, that cleared me up.
Related
My (partial) DataObject:
class InternalExternalLink extends DataObject {
private static $db = array(
'ExternalLink' => 'VarChar(256)',
'LinkLabel' => 'VarChar(256)',
"LinkType" => "Enum(array('Internal', 'External','Attachment'))"
);
private static $has_one = array(
'InternalLink' => 'SiteTree',
'Attachment' => 'File'
);
function getCMSFields() {
$fields = new FieldList(array(
$internal = DropdownField::create("InternalLinkID", "Choose a page", SiteTree::get()->map()->toArray())->setEmptyString("-- choose --"),
));
return $fields;
}
Add I add this to Page:
class Page extends SiteTree {
private static $has_many = array(
'Links' => 'InternalExternalLink'
);
function getCMSFields() {
$fields = parent::getCMSFields();
$gridField = new GridField('Links', 'Links', $this->Links(), GridFieldConfig_RecordEditor::create());
$fields->addFieldsToTab('Root.Main', $gridField);
return $fields;
}
The problem is when adding Links via the gridfield it automatically assumes that the Link.InternalLink is the parent page, rather than any page, and hides the page select drop down. E.g. if I am editing the about-us page then every Link dataobject I add via the gridfield automatically sets its InternalLink to the about-us page.
How do I change this assumption to allow me to select any page via the dropdown?
Try this:
1) Give the DataObject a "Parent" relation:
class InternalExternalLink extends DataObject {
private static $has_one = array(
'Parent' => 'DataObject',
'InternalLink' => 'SiteTree',
'Attachment' => 'File'
);
...
}
2) Specify "Parent" in the page's has_many:
class LinkTestPage extends SiteTree {
private static $has_many = array(
'Links' => 'InternalExternalLink.Parent'
);
...
}
The problem here is the relation on InternalExternalLink is to SiteTree whereas you're trying to define a relationship back to it on Page. As there's no has_one from InternalExternalLink to Page, and you're using a slightly older version of 3.1, the default has_one of Parent is looked for.
To solve this, you can either change the InternalLink relation to point to Page instead of SiteTree or use a DataExtension to add the has_many relation on to SiteTree.
I have a class "performance", and to each performance other performances can be linked as recommendations. After some trial and error I have something semi-working, using the ORM:
public static $many_many = array(
'Recommendations' => 'Performance'
);
public static $belongs_many_many = array(
'Recommendations' => 'Performance'
);
The above lets me specify recommendations, but in normal many to manies (between two classes), the relation the other way around is also available. Here it is not. Any clue on how to make the inverse relation visible on the "Performance" class without creating the inverse many-to-many and manually inserting the inverse relation there?
Update(May 22, 2014): For now I've come to the conclusion that there isn't an out of the box solution to this problem. I made the following minimal example, based on #FinBoWa's solution, and that shows the "Inverse component of Degree.InterestingDegreesReversed not found (Degree)":
class Degree extends DataObject {
private static $db = array(
"Name" => "Varchar(255)",
);
private static $many_many = array(
'InterestingDegrees' => 'Degree'
);
private static $belongs_many_many = array(
'InterestingDegreesReversed' => 'Degree.InterestingDegrees'
);
}
class DegreeAdmin extends ModelAdmin {
public static $managed_models = array('Degree');
static $url_segment = 'degrees';
static $menu_title = 'Degrees';
}
I also tried #g4b0's solution, but that has the serious drawback that it does not show the reverse relationship in the admin. For now I am using his solution, but it is not a real solution to the problem. Therefore I will not accept an answer for now...
You have to specify two different names for many_many and belongs_many_many, so you can access them. For example:
public static $many_many = array(
'RelatedRecommendations' => 'Performance'
);
public static $belongs_many_many = array(
'Recommendations' => 'Performance'
);
You should be able to use the dot notation in the relations.
One of our projects had degrees and we wanted to relate the interesting degrees using the same class:
private static $many_many = array(
'InterestingDegrees' => 'Degree'
);
private static $belongs_many_many = array(
'InterestingDegreesReversed' => 'Degree.InterestingDegrees'
);
This works if you add the editor manually when relating site trees.
But if you use model admin, you have to declare the fields manually otherwise you will get erros. So a working example if you are using model admin would be:
class Degree extends DataObject {
private static $db = array(
"Name" => "Varchar(255)",
);
private static $many_many = array(
'InterestingDegrees' => 'Degree'
);
private static $belongs_many_many = array(
'InterestingDegreesReversed' => 'Degree.InterestingDegrees'
);
public function getCMSFields() {
$fields = new FieldList();
$fields->add(new TextField("Name"));
// dont show the field untill we have something to relate to
if($this->ID){
//Interesting degrees
$gfc = GridFieldConfig_RelationEditor::create();
//Remove add and edit features
$gfc->removeComponentsByType('GridFieldAddNewButton');
$gfc->removeComponentsByType('GridFieldEditButton');
$gf = new GridField('InterestingDegrees', null, $this->InterestingDegrees(), $gfc);
$fields->add($gf);
}
return $fields;
}
}
class DegreeAdmin extends ModelAdmin {
public static $managed_models = array('Degree');
static $url_segment = 'degrees';
static $menu_title = 'Degrees';
}
I've gone through both ideas, and this is what I've come up with for a project I'm working on (I'm using the Brand model).
This is inspired by both solutions, the only additional item I'm adding is the onAfterWrite method. There I'm just looping through all brands, and adding the other side.
This takes for all brands to be edited through only the Brands tab. The other should be removed in getCMSFields like this: $fields->removeByName('RelatedBrands');.
<?php
class Brand extends DataObject {
private static $many_many = [
'Brands' => 'Brand' //part 1 of brand-brand relation
];
private static $belongs_many_many = [
'RelatedBrands' => 'Brand' //part 2 of brand-brand relation
];
public function onAfterWrite() {
parent::onAfterWrite();
foreach ($this->Brands() as $brand) {
$this->RelatedBrands()->add($brand);
}
}
}
I'm new to Yii and I have a table 'Student' with fields like 'stdStudentId', 'stdName', etc.
I'm making API, so this data should be returned in JSON. Now, because I want field names in JSON to just be like 'id', 'name', and I don't want all fields returned, i made a method in the model:
public function APIfindByPk($id){
$student = $this->findByPk($id);
return array(
'id'=>$student->stdStudentId,
'name'=>$student->stdName,
'school'=>$student->stdSchool
);
}
The problem is, stdSchool is a relation and in this situation, $student->stdSchool returns array with fields like schSchoolId, schName, etc. I don't want fields to be named like that in JSON, and also I don't want all the fields from School returned and I would like to add some fields of my own. Is there a way to do this in Yii, or I'll have to do it manually by writing methods like this?
I have been looking for the same thing. There is a great php lib named Fractal letting you achieve it: http://fractal.thephpleague.com/
To explain briefly the lib, for each of your models you create a Transformer that will be doing the mapping between your model attributes and the ones that need to be exposed using the api.
class BookTransformer extends Fractal\TransformerAbstract
{
public function transform(Book $book)
{
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => $book->yr,
];
}
}
In the transformer you can also set the relation that this model have :
class BookTransformer extends TransformerAbstract
{
/**
* List of resources relations that can be used
*
* #var array
*/
protected $availableEmbeds = [
'author'
];
/**
* Turn this item object into a generic array
*
* #return array
*/
public function transform(Book $book)
{
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => $book->yr,
];
}
/**
* Here we are embeding the author of the book
* using it's own transformer
*/
public function embedAuthor(Book $book)
{
$author = $book->author;
return $this->item($author, new AuthorTransformer);
}
}
So at the end you will call
$fractal = new Fractal\Manager();
$resource = new Fractal\Resource\Collection($books, new BookTransformer);
$json = $fractal->createData($resource)->toJson();
It's not easy to describe all the potential of fractal in one answer but you really should give it a try.
I'm using it along with Yii so if you have some question don't hesitate!
Since you are getting the values from the database using Yii active record, ask the database to use column aliases.
Normal SQL would be something like the following :
SELECT id AS Student_Number, name AS Student_Name, school AS School_Attending FROM student;
In Yii, you can apply Criteria to the findByPK() function. See here for reference : http://www.yiiframework.com/doc/api/1.1/CActiveRecord#findByPk-detail
$criteria = new CDbCriteria();
$criteria->select = 'id AS Student_Number';
$student = Student::model()->findByPk($id, $criteria);
Note that in order to use a column alias like that, you will have to define a virtual attribute Student_Number in your Student{} model.
Override the populateRecord() function of ActiveRecord can achieve this!
My DishType has 5 properties and override the populateRecord function Yii would invoke this when records fetched from db.
My code is here!
class DishType extends ActiveRecord
{
public $id;
public $name;
public $sort;
public $createTime;
public $updateTime;
public static function populateRecord($record, $row)
{
$pattern = ['id' => 'id', 'name' => 'name', 'sort' => 'sort', 'created_at' => 'createTime', 'updated_at' => 'updateTime'];
$columns = static::getTableSchema()->columns;
foreach ($row as $name => $value) {
$propertyName = $pattern[$name];
if (isset($pattern[$name]) && isset($columns[$name])) {
$record[$propertyName] = $columns[$name]->phpTypecast($value);
}
}
parent::populateRecord($record, $row);
}
}
I have 4 tables of order payments user and profiles. Payments has a belongs_to relation with order. Order has a belongs_to relation with user, and user has_many profiles.
While displaying payments in cgridview I need to display the firstname and lastname of user stored in profile.
I tried using:
$data->order->user->profiles->firstname;
Also tried to add parameter firstname to the model class of Payment and tried to create the setter method as:
public function getFirstname(){
if ($this->_firstname=== null && $this->order !== null) {
$this->_firstname = $this->order->user->profiles->firstname;
}
return $this->_firstname ;
}
public function setFirstname($value){
$this->_firstname = $value ;
}
But I have not been able to get the desired result.
Edit: the search method has the following code:
public function search() {
$criteria = new CDbCriteria;
$criteria->with = array('order.user.profiles') ;
. . . .
$criteria->compare('firstname', $this->_firstname, true);
. . . .
return new CActiveDataProvider($this, array(
'criteria' => $criteria,
));
}
I would suggest using the "through" relation as it makes life easier. All you have to do is, goto your "payments" model and add the following relations,
public function relations()
{
return array(
'order' => array(self::BELONGS_TO, 'Orders', 'order_id'),
'user'=>array(
self::BELONGS_TO,'User',array('user_id'=>'id'),'through'=>'order'
),
'profiles'=>array(
self::HAS_MANY,'Profile',array('id'=>'user_id'),'through'=>'user'
),
);
}
and in the grid you can access the first_name by using,
$data->profiles[0]->firstname
try this in model:
public function getFirstname(){
return $this->order->user->profiles->firstname;
}
and in the grid:
$data->firstname;
I have problem in CGrid while on sorting a relational data using relational model in `` page.
Briefly my scenario:
I have a user model: Entities=> id,username
And a profile Model: Entities=> id, firstname,lastname, user_id,etc..
I want to list profile model and username from user model in CGrid, so that sorting and searching perms well. In my case sorting username is done by user_id not by username. I want to search it by username,so i do the following,
My Controller Action:
$model = new Profile('search');
$model -> unsetAttributes();// clear any default values
if (isset($_GET['Profile']))
$model -> attributes = $_GET['Profile'];
$this -> render('MyPage', array('model' => $model ));
My Model Relation:
public function relations() {
// NOTE: you may need to adjust the relation name and the related
// class name the relations automatically generated below.
return array(
'user' => array(self::BELONGS_TO, 'user', 'user_id'),);
}
Model Rules:
array( 'xxx,yyy,user_name', 'safe', 'on'=>'search' ),
And model search function
if(!empty($this->user_id)){
$criteria->with='user';
$criteria->order = ::app()->request->getParam('sort');// 'username ASC'
}
$criteria -> compare('user.username', $this->user_id, true);
My
$this->widget('zii.widgets.grid.CGrid', array(
'id'=>'profile-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
array('name'=>'user_id',
'header'=>User::model()->getAttributeLabel('username'),
'value' =>'$data->getRelated(\'user\')->username',
'type'=>'raw',
'htmlOptions'=>array('style'=>'text-align: center'),),
---------------
During sorting,sorting works perfectly but sorting is done on the basis of user_id not by username. Anything that i am missing to do so. Please suggest.
Reference:Here (I also tried as by declaring a public variable as suggesting in the link but bot workingg.)
Edit: After Issue Fixed.
Thanks for this link too.
Well, the wiki page you found is really a good start...
Here is an alternative way for doing this :
In your Profile model :
// private username attribute, will be used on search
private $_username;
public function rules()
{
return array(
// .....
array('username', 'safe', 'on'=>'search'),
// .....
);
}
public function getUsername()
{
// return private attribute on search
if ($this->scenario=='search')
return $this->_username;
// else return username
if (isset($this->user_id)) && is_object($this->user))
return $this->user->username;
}
public function setUsername($value)
{
// set private attribute for search
$this->_username = $value;
}
public function search()
{
// .....
$criteria->with = 'user';
$criteria->compare('user.username', $this->username, true);
// .....
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
'sort'=>array(
'attributes'=>array(
'username'=>array('asc'=>'user.username', 'desc'=>'user.username DESC'),
'*',
),
),
));
}
And in your view you should simply try :
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'profile-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'columns'=>array(
// .....
'username',
// .....
),
);