Kohana 3.2 ORM define foreign keys consisting of two columns - php

I am building an application on the Kohana framework. For content management, I will be using the Joomla framework, so our copywriters can easily add and edit content in a to them familiar UI.
I have defined several categories to which an article can belong to. E.g. blog (catid = 1) and product (catid = 2). With the xreference column in the joomla content table, the user must assign the article to a specific id in the category selected (because Model_Blog can contain id = 1, as well as Model_Product can).
So every article in Joomla consists of a unique combination of catid and xreference. Now I want to bind this one-to-one relation to my Kohana ORM models (e.g. Model_Blog), but the standard Kohana ORM $_has_one property only supports foreign keys consisting of one column instead of multiple AFAIK.
I tried the following, which, of course, doesn't work:
protected $_has_one = array(
'content' => array('model' => 'cms_content', 'foreign_key' => 'xreference', 'catid' => '1')
);
Can anyone advise me on how to bind this relationship correctly?

After a lookup in the source code, I found out that Kohana ORM doesn't support the feature I described and I had to extend the ORM module (which is because of Kohana's nature, a piece of cake :)), to implement multi-column FK's.
class ORM_Modified extends ORM {
public function __get($column) {
if (isset($this->_has_one[$column])) {
$model = $this->_related($column);
$pk = $this->pk();
// Use this model's primary key value (if no value set) and foreign model's column(s)
if(!is_array($this->_has_one[$column]['foreign_key'])) {
$col = ;
$model->where($model->_object_name.'.'.$this->_has_one[$column]['foreign_key'], '=', $pk);
} else {
foreach($this->_has_one[$column]['foreign_key'] as $col => $value) {
$model->where($model->_object_name.'.'.$col, '=', $value == null ? $pk : $value);
}
}
$model->find();
return $this->_related[$column] = $model;
} else {
return parent::__get($column);
}
}
}
Now I can define an array of columns which form the foreign key in the relationship with the following syntax:
protected $_has_one = array(
'content' => array('model' => 'cms_content', 'foreign_key' => array('xreference' => null, 'catid' => '1'))
);
Please note that my solution only applies to a 1-to-1 relationship, because in my case there's no need to implement it for 1-to-many, although I suspect this would require a similar modification

Related

How to define relation through another relation in Laravel like via() in Yii2?

I need to define one relation through another relation in Laravel so both relations can be eagerly loaded in the optimal way.
For example I have item and attr tables with attr_item intermediate table. In Yii2 I can define one relation through another by using method via:
class Item extends ActiveRecord
{
public function getAttrItems()
{
return $this->hasMany(AttrItem::class, ['item_id' => 'id']);
}
public function getAttrs()
{
return $this-hasMany(Attr::class, ['id' => 'attr_id'])->via('attrItems');
}
}
So when I call $item = Item::find()->with('attrs')->where(['id' => 1])->one() Yii2 makes two additional queries: to table attr_item with condition item_id = 1 and to table attr with identifiers found in previous query. Something like:
SELECT * FROM "item" WHERE "id"=1
SELECT * FROM "attr_item" WHERE "item_id"=1
SELECT * FROM "attr" WHERE "id" IN (1, 2, 3)
After that I have two populated relations: $item->attrs and $item->attrItems.
But I didn't found the same functionality in Laravel. I can define $this->hasManyThrough(Attr::class, AttrItem::class, 'item_id', 'id', 'id', 'attr_id') or $this->belongsToMany(Attr::class, 'attr_item', 'item_id', 'attr_id', 'id', 'id') in Item class but it makes just one query to DB like:
select
"attr".*,
"attr_item"."item_id" as "pivot_item_id",
"attr_item"."attr_id" as "pivot_attr_id"
from
"attr"
inner join "attr_item" on "attr"."id" = "attr_item"."attr_id"
where
"attr_item"."item_id" = '1';
And of course it doesn't hydrate attrItems relation because there is no information about intermediate relation. I can eagerly load both relations attrs and attrItems but in this case the intermediate table attr_item will be used twice and that is not good for performance.
Im not quite sure if this is what you mean but have you looked at 'nested eager loading morph to relationships'?
https://laravel.com/docs/master/eloquent-relationships#eager-loading
Scroll down a bit for 'nested eager loading morph to relationships'.

Yii2 - Gii Model Relations - Why is there a 0 after the function names?

I haven't seen this online when looking at other's code, guides, tutorials, etc.
When I generate a Model with Gii, the functions regarding relations all have a zero after them.
Example:
class Benefit extends \yii\db\ActiveRecord
{
// truncated Yii Model code...
public function getType0()
{
return $this->hasOne(BenefitTypes::className(), ['id' => 'type']);
}
}
BenefitTypes is an id to name mapping:
id | name
---------------
1 => Federal
2 => Non-Profit
In the 'benefit' table, it has column named 'type' that is a relation to the 'benefit_types' table 'id' column.
I though I should be able to do (in /views/benefit/index.php) 'type.name' but it doesn't work either. It changes the column name to "Type Name" and puts "(not set)" in the data table...
Example:
<?= DetailView::widget([
'model' => $model,
'attributes' => [
'id',
'somevalue',
'type.name',
],
]) ?>
What is going on, why does it not act like it's supposed to?
UPDATE
I am beginning to think the 0 suffix to the relation function names, ie: getType0, is due to "type" being used in the table as a column name to avoid duplication or confusion. I can't find this documented though, so would like to have a definite answer on that.
I changed the function name to getTypeRelation(). Then in the index.php view, for the detailview widget, used 'typeRelation.name' and it returned the name through the relation just fine.
Your thinking is correct. Generation of the relation names is done by the function generateRelationName().
protected function generateRelationName($relations, $table, $key, $multiple)
{
if (!empty($key) && substr_compare($key, 'id', -2, 2, true) === 0 && strcasecmp($key, 'id')) {
$key = rtrim(substr($key, 0, -2), '_');
}
if ($multiple) {
$key = Inflector::pluralize($key);
}
$name = $rawName = Inflector::id2camel($key, '_');
$i = 0;
while (isset($table->columns[lcfirst($name)])) {
$name = $rawName . ($i++);
}
while (isset($relations[$table->fullName][$name])) {
$name = $rawName . ($i++);
}
return $name;
}
Yii uses the related table's name as the relation name. Should you have a column with the same name as the related table, a digit will be appended to the relation to avoid confusion due to Yii's handling of magic functions. This also occurs if you have two columns or more in a single table related to the same table e.g columns create_user_id, update_user_id and delete_user_id related to table user will result in relations named user, user0 and user1.
For your example, it is advisable to name your foreign key field something else e.g type_id or typeId. Yii will handle these correctly . The other alternative when you have multiple columns related to the same table is to just rename the functions.
Because relational column name and the relation name is same. When you call $benefit->type what you will expect, the value of the column/property type or the instance of BenefitTypes? So now you know. $benefit->type return the property value and $benefit->type0 returns the instance of relation.

Relational Databases in Yii

So I've tried this: http://www.yiiframework.com/wiki/285/accessing-data-in-a-join-table-with-the-related-models
Basically I have a table called User which relates to ToolAccess; related via a primary key on User and a field for userID on ToolAccess. Now tool access relates to the table Tool which contains a ToolID. Now this doesn't work in Yii, I can't seem to get the toolName field off of the tool table using Yii. Any ideas on how to do this on a Active Record?
I'm using giix if that matters.
Relations code:
public function relations() {
return array(
'usergalleries' => array(self::HAS_MANY, 'Usergallery', 'userid'),
'userinroles' => array(self::HAS_MANY, 'Userinroles', 'userid'),
'userfailedlogin' => array(self::HAS_MANY, 'Userfailedlogin','userid'),
// table name, relation, class name, relation key
'toolaccess' =>array(self::HAS_MANY, 'Toolaccess','userid'),
'tool' =>array(self::HAS_MANY, 'Tool','toolid')
);
}
I'm assuming your schema looks something like this:
User table tool_access table Tool table
id | other columns userid | toolid id | name | other columns
In this case, the User model should have a relation like this (note that the tools will be ordered by name in this case):
public function relations() {
return array(
// other relations here...
'tools' =>array(self::MANY_MANY, 'Tool', 'tool_access(userid,toolid)',
'order' => 'tools.name',
),
);
}
and the code to read the tools should look like this:
$user = User::model()->with('tools')->findByPk($id);
foreach($user->tools as $tool) {
echo $tool->name;
}
I used eager loading of the tools here mostly because of personal preference, using lazy loading should work just as well in this case. But eager loading should be preferred whenever you're processing multiple User records at once.
So if I have understood it properly, user and tool are related in a many-to-many relationship by their primary keys.
So you should define this relationship in the User model like:
'tools' => array(self::MANY_MANY, 'Tool', 'tool_access(userid, toolid)', 'index' => 'id'),
This way you can access the name of the tool after getting the user model
$user = User::model->findByPk($id);
$tools = $user->tools;
foreach ($tools as $tool)
{
echo $tool->name;
}
I hope it works for you.

Kohana ORM: Validate the $_belongs_to relationship exists

I'm trying to get some validation setup for one of my ORM models.
I have 2 tables: parent and children. In the children table, there is a column called 'parent' whose value is the primary ID of a row in the parent table.
What I'm trying to do, is create a validation rule that checks the parent ID specified actually exists in the parent table.
Is there an easy way to do this?
Well I did come up with one solution. I created a static method in my Model class that accepts an ID as a parameter and just checks if the row exists.
So my Model_Child has a rules function like so:
public function rules()
{
return array(
'parent' => array(
// will call Model_Parent::exists($value)
array(array('Model_Parent', 'exists'))
)
);
}
Then my Model_Parent has the following:
public static function exists($id) {
$results = DB::select('*')->from('parent')->where('id', '=', $id)->execute()->as_array();
if(count($results) == 0)
return FALSE;
else
return TRUE;
}
This is working for me. Is there is a more elegant or proper solution?
In MySQL (since this question is tagged mysql) you define a foreign key relationship between parent and child table and leave that problem to the system.

Kohana 3 ORM Relationships Question

I've been through several sites (including this one), and unfortunately as a Kohana newbie I still can't get this to work. The data relationship is fairly simple, I have a company record, which should be linked to 1 status record and 1 type record. Of course there will be multiple companies in the table, but each company is only allowed to be linked to 1 of each (and must be).
What I have is:
class Model_Company extends ORM
{
protected $_has_one = array(
'companystatus' => array('model' => 'companystatus', 'foreign_key' => 'entryid'),
'companytype' => array('model' => 'companytype', 'foreign_key' => 'entryid')
,
);
}
Company Status Model:
<?php defined('SYSPATH') or die('No direct access allowed.');
class Model_CompanyStatus extends ORM
{
protected $_table_name = 'datadictionary';
protected $_primary_key = 'entryid';
protected $_has_many = array(
'company' => array('foreign_key' => 'statusid')
,
);
}
?>
Company Type Model:
<?php defined('SYSPATH') or die('No direct access allowed.');
class Model_CompanyType extends ORM
{
protected $_table_name = 'datadictionary';
protected $_primary_key = 'entryid';
protected $_has_many = array(
'company' => array('foreign_key' => 'companytypeid')
,
);
}
?>
The companystatus and companytype models are mapped to a single table which has 2 fields, entryid and entryname. This table is called "datadictionary", and has the appropriate properties so that I don't have to use "id" as the record id field.
Now I load my Company record like this:
$company = ORM::factory('company')
->where('id', '=', 1)
->where('hasbeendeleted', '=', 0)
->find();
The problem is that I don't get anything back for the companystatus and companytype properties for the company, and when I do a $company->companystatus->find() I get the first record returned, which is weird. What am I missing?
Thanks!!
:-)
Edit:
For simplicity's sake the Companies table has the following fields:
ID (primary key) - auto inc int
CompanyName - varchar(255)
StatusID - int
CompanyTypeID - int
HasBeenDeleted - smallint (0 for false, 1 for true)
DataDictionary Table:
EntryID (primary key) - auto inc int
EntryName - nvarchar(255)
Example Company record:
ID: 1
CompanyName: TestCompany
StatusID: 1
CompanyTypeID: 3
HasBeenDeleted: 0
Example DataDictionary records:
EntryID: 1
EntryName: Active
EntryID: 2
EntryName: Inactive
EntryID: 3
EntryName: Customer
EntryID: 4
EntryName: Supplier
There are a few things here I would try changing.
First of all, for readability, most people use underscores in foreign keys. So instead of entryid, I'd recommend using entry_id (you'd have to make the change in both your database and your code).
In Kohana 3, declaring 'model' => 'companystatus' in a $has_one array is redundant when the key is the same as the model name. You can safely remove that part.
But really, that's all incidental to your problem, which exists somewhere between that last ORM call and your database. (I'm assuming here that hasbeendeleted is a column in the company table, not either of the other two tables you mentioned. Let me know if that's not the case.)
If you're doing a ->where('id', '=', 1) together with a ->find(), you're really expecting to return the one company record if it exists in the database. I would recommend making a separate check for hasbeendeleted.
And speaking of which, instead of naming that variable $companies, it should really be singular (e.g. $company) since it will only hold one record.
And you can simplify ORM::factory('company')->where('id', '=', 1) to simply ORM::factory('company', 1)
If you know for sure that a company with a database ID of 1 exists, then the following code should return that record:
$myCompany = ORM::factory('company', 1);
Then you can do something like if ( ! $myCompany->hasbeendeleted) ...
That should help you a bit. Post more details if you run into trouble.

Categories