I have the following basic schema:
players
id
name
profiles
id
player_id
email
subsets
id
profile_id
alias
I was under the impression the following operation was possible when creating a new record:
Player::create([
'name' => 'Player 1',
'profile.email' => 'player1#email.com',
'profile.subset.alias' => 'Player 1 alias'
]);
Since this code doesn't seem to work, is there anyway to save relationships records together with the create method?
Basically, you can't do this as easy as it looks.
In the docs, all related models are created after the base model is created
$profile = new Profile(array('email' => 'player1#email.com','alias'=>'Player 1 alias'));
$player = new Player(array('name'=>'Player 1'));
$player = $post->profile()->save($profile);
However , if you really want to do it in one go, you can overwrite the save() method in the Player model :
public function save(){
Database::transaction(function() {
$profileModel = new Profile($this->profile);
parent::save();
$this->profile()->insert($profileModel);
});
}
You will then pass the array to the Player method like :
array(
name='Player name',
profile=>array(
email=>'player1#email.com',
subset=>array(
alias=>'Player 1 alias'
)
);
Although this is not a recommended action.
Please read more about how to save the Eloquent models and relationships :
Tutorial 1
Tutorial 2
Everywhere is suggested to create the base model first, then the related models.
Related
I'm trying to implement a way to get the details of a person depending on the group it belongs to.
My database looks like this:
persons:
id
group
type
1
person
9
2
company
30
3
person
9
and so on.
Each "group" has a model which contains detail information for this record specific to the group.
For example:
persondetails looks like this
id
person_id
firstname
lastname
birthname
1
1
Harry
Example
Bornas
2
3
Henrietta
Example
Bornas
I created models for each table and I'm no trying to implement a relationship which allows me to query a person->with('details') via the person model (for example: for a complete list of all persons no matter which type it is).
For single records I got it working via a simple "if $this->group === person {$this->hasOne()}" relation, which doesn't work for listings.
I tried to wrap my head around a way to use a polymorphic relationship, so I put the following into the person model:
public function details(){
Relation::morphMap([
'person' => 'App\Models\Persondetail',
'company' => 'App\Models\Companydetail',
]);
return $this->morphTo();
}
and a subsequent
public function person(){
return $this->morphMany(Person::class, 'details');
}
which doesn't work sadly. Where is my thinking error?
As you're not using laravel convention for the keys, you need to define the keys on your relation
public function details()
{
Relation::morphMap([
'person' => 'App\Models\Persondetail',
'company' => 'App\Models\Companydetail',
]);
return $this->morphTo(__FUNCTION__, 'group', 'type');
}
Docs Link:
https://laravel.com/docs/9.x/eloquent-relationships#morph-one-to-one-key-conventions
Based on the reply by https://stackoverflow.com/users/8158202/akhzar-javed I figure it out, but I had to change the code a bit:
Instead of the code in the answer, I had to use the following:
public function details()
{
Relation::morphMap([
'person' => 'App\Models\Persondetails',
'company' => 'App\Models\Companydetail',
]);
return $this->morphTo(__FUNCTION__, 'group', 'id', 'person_id');
}
Is there a way to map field names to a database to a different attribute name in the model? For example, if the database has a field name of customer_id but I wanted to use eloquent in this way Customer::get(['id']) I've tried using the getAttribute method but that is called after eloquent has attempted to get the value.
You can use accessors to work with such attributes, but there's no way to query them this way with core eloquent.
But fear not! Use this package https://github.com/jarektkaczyk/eloquence and you can easily achieve what you want (Mappable in particular):
// Customer model
protected $maps =[
'id' => 'customer_id',
'name' => 'customer_name',
...
];
// then you can do this:
$customer = Customer::where('name', 'whatever')->first();
// calls WHERE customer_name = ? sql
$customer->id; // customer_id column
$customer->name; // customer_name column
$customer->name = 'different name'; // set mutator works as well
It's in heavy development and currently select is not yet supported, but it's matter of day or two. select support has been pushed already.
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.
I'm new to cakePHP and MVC development and trying to create something with cakePHP but can't figure out how to do this :
I'm creating a simple CRUD application which takes in Albums and Songs through simple data entry forms. I created the DB and used the Cake console app to create all the models / controllers etc and it works well. I can CRUD both albums and songs no problem, and the song table in the DB is connected to the album table with a foreign key so all the links and associations are there in the model.
What I want to do is be able to click on an album and see the songs associated with that album, ,but I'm not sure how to go about it. Do I need to add a query in the model, or does that functionality go into the Controller ?
My take is : in the album list make the album names links, which call a |viewAlbum| function in the Songs Controller with the albumID. Not sure where to go from here though ......
Can anyone point me in the right direction ?
Cheers,
Colm
#JohnP Thank you for your reply. How do you create a link to call that function in the controller though ? I have :
echo $html->link(__($album['Album']['title'], true),
array('controller'=>'Songs',
'action'=>'viewAlbum',
$album['Album']['id']));
Where viewAlbum is the name of the function in the songs controller. Any ideas on why this doesn't work ?
Protos -
If I understand correctly -- you're using John's example, and you need to fix the link in your view that calls his controller?
<?
echo $this->Html->link(__($album['Album']['title'], true), array('controller'=>'Album', 'action'=>'viewSongs', $id));
?>
John's example explained how to create a method in the Albums controller, suggested hitting a method in the Songs model that returned the desired results.
So your link would target the Album controller, and its action should be the controller method.
This method makes less sense in the Songs controller, because it requires an Album id. You just want the Album controller to pull associated data from the Songs model / table. John's answer is exactly correct but maybe too complicated if you're just getting started with Cake. John split the needed functionality by putting a method in the Song model, called by a method in the Albums controller, which pulls results for your view to display.
I'm switching this to "fat controller," which is easier to follow for short code but less MVC.
You need a hasMany relationship from Albums to Songs - each Album hasMany Songs:
// ../models/album.php
class Album extends AppModel {
var $name = 'Album';
var $hasMany = array(
'Song' => array(
'className' => 'Song',
'foreignKey' => 'album_id'
)
);
Your controller action will look like this:
// ../controllers/albums_controller.php
function viewSongs($id = null) {
if(isset($id) && $id != null) {
$albums = $this->Album->find('first',
array('conditions'=>array('Album.id'=>$id));
$songs = $this->Album->Song->find('all',
array('conditions'=>array('Song.album_id'=>$id)));
// This returns variables to the view to use
$this->set(compact('albums', 'songs'));
}
}
Your view will be called viewSongs.ctp, and it'll look something like this:
// ../views/albums/viewSongs.ctp
<?php
foreach($albums as $album) {
echo "<h2>{$album['name']}</h2>";
echo "<ul>";
foreach ($songs as $song) {
echo "<li>{$song['Song']['name']}</li>"
}
echo "</ul>";
}
And your link in ../views/albums/view.ctp would be like:
<?php
echo $this->Html->link('View Songs', array('controller'=>'albums',
'action'=>'viewSongs', $id));
?>
Cake's native ORM already does this for you. If you actually go into the view page for an album, it should be showing you all the related songs there it self. This works only if you have setup the relationships properly.
If you want to code this behavior yourself, you could put a viewSongs action in your AlbumController. This method would look at the album ID passed to it, and call a method (e.g. getSongsByAlbum($aid)) in your Song model. Inside that method in your song model would be a call that looks something like
$opts = array(
'conditions' => array(
'album_id' => $aid
)
);
return $this->find('all', $opts);
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.