Multiple Foreign Key to same table Gas Orm - mysql

Since this mornong i am facing a very big problem. I am using CodeIgniter to develop a website, and GAS ORM for the database.
I have basically two tables. One named "pool", and one named "partners". I am having two associations between these two tables, so I have two foreign keys in my table Partners referencing the table pool.
Pool(#id:integer, name:varchar)
Partners(#id:integer, associated_pool_id=>Pool, futur_associated_pool_id=>Pool).
As I have two references to the same table, I can't name the foreign keys "pool_id". So in my relationships with Gas ORM, I have to specify the names of the columns. I do it, but it doesn't work...
Here is what I do:
class Partner extends ORM {
public $primary_key = 'id';
public $foreign_key = array('\\Model\\Pool' => 'associated_pool_id', '\\Model\\Pool' => 'future_associated_pool_id');
function _init()
{
// Relationship definition
self::$relationships = array(
'associated_pool' => ORM::belongs_to('\\Model\\Pool'),
'future_association_pool' => ORM::belongs_to('\\Model\\Pool'),
);
self::$fields = array(
'id' => ORM::field('auto[11]'),
'name' => ORM::field('char[255]'),
'associated_pool_id' => ORM::field('int[11]'),
'future_associated_pool_id' => ORM::field('int[11]')
);
}
and in my Pool class :
class Pool extends ORM {
public $primary_key = 'id';
function _init()
{
// Relationship definition
self::$relationships = array(
'associated_partner' => ORM::has_many('\\Model\\Partner'),
'future_associated_partner' => ORM::has_many('\\Model\\Partner'),
);
self::$fields = array(
'id' => ORM::field('auto[11]'),
'name' => ORM::field('char[50]'),
);
}
I have a test controller testing if everything is okay:
class Welcome extends CI_Controller {
public function index()
{
$pool = \Model\Pool::find(1);
echo $pool->name;
$partners = $pool->associated_partner();
var_dump($partners);
}
But I have an error saying:
Error Number: 1054
Champ 'partner.pool_id' inconnu dans where clause
SELECT * FROM partner WHERE partner.pool_id IN (1)
I don't know how to specify to Gas ORM that it shouldn't take "pool_id" but "associated_pool_id"....
Thank you for your help!!!!!!!!!!!!

I don't know, if this topic is still up to date and interesting to some of you, but in general, I had the exact same problem.
I decided Gas ORM to be my mapper in combination with CodeIgniter. As my database structure was given and it was not following the table_pk convention of Gas, I had to define a foreign key by myself which shall refer to my custom database foreign key. However, the definition of it had no impact on anything. Like your error above, the mapper was not able to build the right SQL-statement. The statement looked similar to yours:
SELECT * FROM partner WHERE partner.pool_id IN (1)
Well, it seems like Gas ignores the self-defined foreign keys and tries to use the default table_pk convention. This means, it takes the table (in your case: pool) and the primary key (id) by merging it with a underscore character.
I figured out, that the constructor of orm.php handles every primary and foreign key defined within the entities. In line 191, the code calls an if clause combined with the empty function of php. As the primary key is defined always and there is no negation in the statement, it skips the inner part of the clause every time. However, the inner part takes care of the self-defined foreign keys.
Long story short, I added a negation (!) in line 191 of orm.php which leads me to the following code:
if ( ! empty($this->primary_key))
{
if ( ! empty($this->foreign_key))
{
// Validate foreign keys for consistency naming convention recognizer
$foreign_key = array();
foreach($this->foreign_key as $namespace => $fk)
{
$foreign_key[strtolower($namespace)] = $fk;
}
$this->foreign_key = $foreign_key;
}
else
{
// If so far we didnt have any keys yet,
// then hopefully someone is really follow Gas convention
// while he define his entity relationship (yes, YOU!)
foreach ($this->meta->get('entities') as $name => $entity)
{
if ($entity['type'] == 'belongs_to')
{
$child_name = $entity['child'];
$child_instance = new $child_name;
$child_table = $child_instance->table;
$child_key = $child_instance->primary_key;
$this->foreign_key[strtolower($child_name)] = $child_table.'_'.$child_key;
}
}
}
}
Well, this little fix helped me out a lot and I hope some of you can take advantage of this hint as well.

Related

Yii2 REST API update (put) when composite key

I am trying to update my model via PUT http request in Yii2 framework.
Everything works fine when I have single Primary Key in my model.
Problems are when I have composite primary key in table.
How to update?
I submit JSON:
{"date_execution":"2017-08-26","order_id":"59", "company_id":13,"your_price":100,"car_id":"8","note":"lorem ipsum"}
my composite primary key include:
- order_id
- company_id
I tried following requests:
PUT SERVER/offer/100 - where 100 is company_id
PUT SERVER/offer/2000 - where 2000 is order_id
those 2 requests are returning problem:
{"name":"Not Found","message":"Object not found: 13","code":0,"status":404,"type":"yii\\web\\NotFoundHttpException"}
I also tried
PUT SERVER/offer/2000/100 - where 2000 is order_id and 100 is company_id
PUT SERVER/offer/100/2000
those 2 return controller/action not found exception
Also I added order_id and company_id to JSON,
but nothing works.
Controller Class:
use yii\rest\ActiveController;
class OfferController extends ActiveController
{
// adjust the model class to match your model
public $modelClass = 'app\models\Offer';
public function behaviors(){
$behaviors = parent::behaviors();
// remove authentication filter
$auth = $behaviors['authenticator'];
unset($behaviors['authenticator']);
// add CORS filter
$behaviors['corsFilter'] = [
'class' => CustomCors::className()
];
// re-add authentication filter
$behaviors['authenticator'] = [
'class' => CompositeAuth::className(),
'authMethods' => [
HttpBearerAuth::className(),
],
];
// avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
$behaviors['authenticator']['except'] = ['options'];
return $behaviors;
}
}
It should work if you use PUT SERVER/offer/2000,100
You can print primaryKey() of the model to know the order of the keys.
You can see it in the docs here
https://www.yiiframework.com/doc/api/2.0/yii-rest-action
If composite primary key, the key values will be separated by comma.
yii\rest\UpdateAction uses ActiveRecord::findModel() method to load data. There is an answer in those phpdoc:
If the model has a composite primary key, the ID must be a string of
the primary key values separated by commas
So the right resource is (considering the first key field in the table structure is company_id)
PUT SERVER/offer/100,2000
You firstly need to add primaryKey() in the model, to override the default primaryKey() of the ActiveRecord class. This function needs to return your composite primary key.
What you need to do the model thus would be
primaryKey()
{
return array('company_id', 'order_id');
}

What does _fpt_ stand for in semantic mediawiki

In database I have two types of tables [based on their name]
Example:
1. smw_di_... => [semantic mediawiki data item => as I understood]
2. smw_fpt_... => [??]
And what is the key s_id
Does someone know?
It's important to me to understand the logic, because no books are available, no documentation...
FPT - Fixed Property Table
Fixed Properties are properties that are user defined but intensively used in the wiki
extensions/SemanticMediaWiki/src/SQLStore/PropertyTableInfoFetcher.php
private $fixedSpecialProperties = array(
// property declarations
'_TYPE', '_UNIT', '_CONV', '_PVAL', '_LIST', '_SERV',
// query statistics (very frequently used)
'_ASK', '_ASKDE', '_ASKSI', '_ASKFO', '_ASKST', '_ASKDU',
// subproperties, classes, and instances
'_SUBP', '_SUBC', '_INST',
// redirects
'_REDI',
// has sub object
'_SOBJ',
// vocabulary import and URI assignments
'_IMPO', '_URI',
// Concepts
'_CONC'
);
s_id => pointer [foreign key to smw_object_ids smw_id]
p_id => property id if it's not fixed DI

Recursive in cakephp3?

here is my table association code:
class UserMastersTable extends Table {
public function initialize(array $config) {
parent::initialize($config);
$this->table('user_masters');
$this->hasOne('person_masters', [
'className' => 'person_masters',
'foreign_key'=>'user_master_id',
'dependent' => true
]);
}
}
when in controller i am using:
$this->UserMasters->get($id);
it results only user_masters table data..
so how can i also get Associated tables data??
Use contain()
Copy-Paste from the manual:
You should use contain() when you want to load the primary model, and
its associated data. While contain() will let you apply additional
conditions to the loaded associations, you cannot constrain the
primary model based on the associations. For more details on the
contain(), look at Eager Loading Associations.
// In a controller or table method.
// As an option to find()
$query = $articles->find('all', ['contain' => ['Authors', 'Comments']]);
// As a method on the query object
$query = $articles->find('all');
$query->contain(['Authors', 'Comments']);
Read the manual before jumping into trial and error driven development! If you would have done one of the tutorials in the manual before this would be clear. So do them now, they'll cover a lot more of the basics.

Yii model: Dynamic table relations

Table.linkedIndex is related to LinkedIndex.ID. The value of the field LinkedIndex.TableName is either Linked1 or Linked2 and defines which of these tables is related to a row in Table.
Now i want to make a dynamical link with Yii models so that i can easily get from a Table row to the corresponding Linked1 or Linked2 row:
Table.linkedID = [LinkedIndex.TableName].ID
Example
Table values:
LinkedIndex values:
Now I should get the row from Linked2 where ID=2:
$model = Table::model()->findByPk(0);
$row = $model->linked;
Model
In the model Table, I tried to make the relation to the table with the name of the value of linkedIndex.TableName:
public function relations()
{
return array(
'linkedIndex' => array(self::HAS_ONE, 'LinkedIndex', array('ID' => 'linkedIndex')),
'linked' => array(
self::HAS_ONE,
'linkedIndex.TableName',
array('ID' => 'linkedID'),
)
)
}
But then I get the error:
include(linkedIndex.TableName.php) [function.include]: failed to open stream: No such file or directory
Is there any way to make a dynamic relation Table.linkedID -> [LinkedIndex.TableName].ID with Yii Models?
Per the Yii docs here:
http://www.yiiframework.com/doc/api/1.1/CActiveRecord#relations-detail
I'd suggest using self::HAS_ONE instead (unless there can be multiple rows in LinkedIndex with the same ID - although from the looks of above, I doubt that's the case).
You can link tables together that have different keys by following the schema:
foreign_key => primary_key
In case you need to specify custom PK->FK association you can define it as array('fk'=>'pk'). For composite keys it will be array('fk_c1'=>'pk_с1','fk_c2'=>'pk_c2').
so in your case:
public function relations(){
return array(
'linkedIndex' => array(self::HAS_ONE, 'LinkedIndex', array('ID' => 'linkedIndex')),
);
}
where LinkedIndex is the class name for the LinkedIndex model (relative to your Table model - i.e. same folder. You could change that, of course) and array('ID' => 'linkedIndex') specifies the relationship as LinkedIndex.ID = Table.linkedIndex.
Edit
Looking at your updated example, I think you're misunderstanding how the relations function works. You're getting the error
include(linkedIndex.TableName.php) [function.include]: failed to open stream: No such file or directory
because you're trying to create another relation here:
'linked' => array(
self::BELONGS_TO,
'linkedIndex.TableName',
array('ID' => 'linkedID'),
)
This part: linkedIndex.TableName refers to a new model class linkedIndex.TableName, so Yii attempts to load that class' file linkedIndex.TableName.php and throws an error since it doesn't exist.
I think what you're looking for is to be able to access the value TableName within the table LinkedIndex, correct? If so, that's accessible from within the Table model via:
$this->linkedIndex->TableName
This is made possible by the relation we set up above. $this refers to the Table model, linkedIndex refers to the LinkedIndex relation we made above, and TableName is an attribute of that LinkedIndex model.
Edit 2
Per your comments, it looks like you're trying to make a more complex relationship. I'll be honest that this isn't really the way you should be using linking tables (ideally you should have a linking table between two tables, not a linking table that says which 3rd table to link to) but I'll try and answer your question as best as possible within Yii.
Ideally, this relationship should be made from within the LinkedIndex model, since that's where the relationship lies.
Since you're using the table name as the linking factor, you'll need to create a way to dynamically pass in the table you want to use after the record is found.
You can use the LinkedIndex model's afterFind function to create the secondary link after the model is created within Yii, and instantiate the new linked model there.
Something like this for your LinkedIndex model:
class LinkedIndex extends CActiveRecord{
public $linked;
public static function model($className = __CLASS__){
return parent::model($className);
}
public function tableName(){
return 'LinkedIndex';
}
public function afterFind(){
$this->linked = new Linked($this->TableName);
parent::afterFind();
}
//...etc.
}
The afterFind instantiates a new Linked model, and passes in the table name to use. That allows us to do something like this from within the Linked model:
class Linked extends CActiveRecord{
private $table_name;
public function __construct($table_name){
$this->table_name = $table_name;
}
public static function model($className = __CLASS__){
return parent::model($className);
}
public function tableName(){
return $this->table_name;
}
//...etc.
}
which is how we dynamically create a class with interchangeable table names. Of course, this fails of the classes need to have separate operations done per-method, but you could check what the table_name is and act accordingly (that's pretty janky, but would work).
All of this would result in being to access a property of the linked table via (from within the Table model):
$this->linkedIndex->linked->foo;
Because the value of LinkedIndex.TableName and Table.linkedID is needed to get the values, I moved the afterFind, suggested by M Sost, directly into the Table-Class and changed its content accordingly. No more need for a virtual model.
class Table extends CActiveRecord {
public $linked; // Needs to be public, to be accessible
// ...etc.
public function afterFind() {
$model = new $this->linkedIndex->TableName;
$this->linked = $model::model()->findByPk( $this->linkedID );
parent::afterFind();
}
// ...
}
Now I get the row from Linked2 where ID=2:
$model = Table::model()->findByPk(0);
$row = $model->linked;

Foreign Key In MySQL using Yii

I have the database just like this
==== Invoices ====
id
costumer_id
description
==== Costumers ===
id
firstname
lastname
Now I have made the relation in between models just like this.In Invoices models the relation is as like this
public function relations()
{
return array(
'customer' => array(self::BELONGS_TO, 'Customer', 'customer_id')
);
}
In costumer model the relation is just like this
public function relations()
{
return array(
'invoice' => array(self::HAS_MANY, 'Invoices','customer_id')
);
}
Now as my relation is defined one costumer has many invoices and the invoice is belongs to the costumer.
Now I made multimodel and loaded the Costumer model into Invoice model just like this.
public function actionCreate()
{
$model = new Invoices;
$customers = new Customers;
// Uncomment the following line if AJAX validation is needed
// $this->performAjaxValidation($model);
if (isset($_POST['Invoices'],$_POST['Customers']))
{
$model->attributes = $_POST['Invoices'];
$customers->attributes = $_POST['Customers'];
$valid = $model->validate();
$valid = $customers->validate();
if($valid)
{
$model->save(false);
$customers->id = $model->customer_id;
$customers->save(false);
$this->redirect(array('view','id'=>$model->id));
}
}
$this->render('create',array(
'model'=>$model,
'customers'=>$customers,
));
}
Here every thing is okay. I can insert the data for both models easily. But my problem comes here in the way that when I am inserting data from Invoice multimodel the foreign key id is not changing. It is showing zero everytime. Can some one tell me where I am wrong.Any help and suggestions will be highly appriciable.
My guess is that you are overriding the customer's primary key with the invoice's foreign key. I do not say that's not correct that way (maybe in your scenario it makes sense).
Let me explain what you are doing in that code:
First, you create new instances of two models, Invoices and Customers. Yii understands that as "they wish to insert new items in the database".
Then, you check if there are the items coming from an ajax form. If true, then,
You populate Invoices (defined as $model. I'd change it to $invoice, in case you need to edit and understand it further).
You also popupulate the customer's information, overriding the $valid value (so, you don't know if invoice is actually valid).
If valid (remember you're only validating customer's information), do,
Save the invoice
Override customer's id with invoice's foreing key to customer.
Save the customer, and redirect.
Now, what I got from that:
$valid doesn't work as expected: I'd change that to an incremental assignment.
You may not be passing a customer_id coming from the ajax form. Foreing keys are integers, and so if not defined within a model, it becomes 0 or NULL.
You are always passing id = 0 / NULL to Customer's model, so it would probably warn you when validating. However, you are using save(false), which means it doesn't pre-validate on save, so you never know it doesn't work.
So, according to this:
public function actionCreate()
{
$invoice = new Invoices;
$customers = new Customers;
// Uncomment the following line if AJAX validation is needed
// $this->performAjaxValidation($invoice);
if (isset($_POST['Invoices'],$_POST['Customers']))
{
$invoice->attributes = $_POST['Invoices'];
$customers->attributes = $_POST['Customers'];
$valid = true; /* expect it is always valid */
$valid &= $invoice->validate(); /* if $invoice is not valid, $valid will be false (true&false = false) */
$valid &= $customers->validate(); /* same as the above line */
if($valid)
{
$customers->save(); /* First save customers. It's the Foreign item */
$invoice->customer_id = $customers->getPrimaryKey(); /* new instances use getPrimaryKey() to get its id */
$invoice->save(); /* Save invoice AFTER getting customer's primary key */
$this->redirect(array('view','id'=>$invoice->id));
}
}
$this->render('create',array(
'invoice'=>$invoice,
'customers'=>$customers,
));
}
I hope this solves your problem.
Please you need to understand a clear scenerio here. why would you use
if($valid)
{
$model->save(false);
$customers->id = $model->customer_id;
$customers->save(false);
$this->redirect(array('view','id'=>$model->id));
}
$model->save(false); tells model that if this record is not save(), the it shoud set the $customers->id = $model->customer_id;
This will only return false because. I do rather prefer if you call ( $customers->id = $model->customer_id;) before the $model->save();
REMEMBER, if you need to check if Save() returns true, then set it to $model->save(true)