New to CakePHP/Model use - cakephp

I have a table/model of users, and a certain subset of those users have an entry in a "roles" table.
What is the standard way to create a model that has access to all the users who have one or more entries/rows in the roles table?
Thanks!

Welcome to Cake!
One of the coolest and most powerful aspects of Cake is their very capable ORM. Once you figure out Cake's associations and the way they work with databases it's hard to go back to something else. But, there's a couple things we need to make sure we do with our actual physical database schema before we can do this.
So, let's work with some "sample" data.
users
-------------------------------------------------------
| id (PK) | role_id (FK) | username | password |
-------------------------------------------------------
| 1 | 1 | cspray | ad64675 |
-------------------------------------------------------
...and so on and so on.
roles
--------------------------------------------
| id (PK) | name |
--------------------------------------------
| 1 | Benevolent Dictator for Life |
--------------------------------------------
...and so on and so on.
Note the name of the tables, users and roles, and the field names, specifically id and role_id. Those are important. id is normally an auto increment, int field in your database. There's other ways to store your id key but let's not pile too much on at once.
Now, all this and we haven't answered your question yet! Don't worry we're about to get to that. But, first double check your database schema and make sure those table names and columns are named according to Cake convention. Don't worry I'll wait...
...
Ok! Now open up your favorite text editor and navigate to install_dir/app/models/ (where install_dir is the name of the folder you have the framework installed in). Once there create a new file and name it user.php. Inside this file we're gonna use some code that looks like this...
class User extends AppModel {
public $belongsTo = array('Role');
}
And, well, there you have it! You're now free to query the roles table from within your User class by calling methods through Cake's "association chain", $this->User->Role->find(). You can, and probably should, create a Role model just like you created User, sans the $belongsTo. Inside it you can define your own methods to interact with the roles table and give the information you need to User.
I don't want to go too much into how Cake's ORM works though, mostly because if you read through the Cake Cookbook and the Cake API you will find a wealth of knowledge ready to be plucked.
Enjoy your ventures into Cake!
So, your model associations are all setup and you're ready to go. But, where do you go? Well, you'll want to check out Cake's Model behaviors, particularly one of the most important core behaviors, Containable. This will allow you to do exactly what you want to do.
Let's just take a look at some code, I'm going to assume that you're already familiar with the Model::find() method. If not I'd go check that out real quick so you don't get too confused. It's fairly straight-forward though.
class User extends AppModel {
// provides $this->User->Role
public $belongsTo = array('Role');
// gives access to various behaviors
public $actsAs = array('Containable');
// custom function
public function get_users_with_roles() {
return $this->User->find(
'contain' => array(
'Role' => array( // tells ORM to query `roles` table
'Hour', // <-- tells ORM to query `hours` table
'conditions' => array(
'Role.id' => '<> NULL'
)
)
),
'fields' => array(
'User.username'
)
);
}
}
This should return all user's usernames (and possibly their id and role_id as well) that have an entry in the roles table. Note the addition of the new property and the way the "query" is laid out in Cake's ORM. One thing I could suggest getting comfortable with is multi-dimensional associative arrays, Cake uses a lot of them.

Related

How to ensure developers filter by a foreign key in CakePHP

In a legacy project we had issues where if a developer would forget a project_id in the query condition, rows for all projects would be shown - instead of the single project they are meant to see. For example for "Comments":
comments [id, project_id, message ]
If you forget to filter by project_id you would see all projects. This is caught by tests, sometimes not, but I would rather do a prevention - the dev should see straightaway "WRONG/Empty"!
To get around this, the product manager is insisting on separate tables for comments, like this:
project1_comments [id,message]
project2_comments [id,message]
Here if you forgot the project/table name, if something were to still pass tests and got deployed, you would get nothing or an error.
However the difficulty is then with associated tables. Example "Files" linked to "Comments":
files [ id, comment_id, path ]
3, 1, files/foo/bar
project1_comments
id | message
1 | Hello World
project2_comments
id | message
1 | Bye World
This then turns into a database per project, which seems overkill.
Another possibility, how to add a Behaviour on the Comments model to ensure any find/select query does include the foreign key, eg - project_id?
Many thanks in advance.
In a legacy project we had issues where if a developer would forget a project_id in the query condition
CakePHP generates the join conditions based upon associations you define for the tables. They are automatic when you use contains and it's unlikely a developer would make such a mistake with CakePHP.
To get around this, the product manager is insisting on separate tables for comments, like this:
Don't do it. Seems like a really bad idea to me.
Another possibility, how to add a Behaviour on the Comments model to ensure any find/select query does include the foreign key, eg - project_id?
The easiest solution is to just forbid all direct queries on the Comments table.
class Comments extends Table {
public function find($type = 'all', $options = [])
{
throw new \Cake\Network\Exception\ForbiddenException('Comments can not be used directly');
}
}
Afterwards only Comments read via an association will be allowed (associations always have valid join conditions), but think twice before doing this as I don't see any benefits in such a restriction.
You can't easily restrict direct queries on Comments to only those that contain a product_id in the where clause. The problem is that where clauses are an expression tree, and you'd have to traverse the tree and check all different kinds of expressions. It's a pain.
What I would do is restrict Comments so that product_id has to be passed as an option to the finder.
$records = $Comments->find('all', ['product_id'=>$product_id])->all();
What the above does is pass $product_id as an option to the default findAll method of the table. We can than override that methods and force product_id as a required option for all direct comment queries.
public function findAll(Query $query, array $options)
{
$product_id = Hash::get($options, 'product_id');
if (!$product_id) {
throw new ForbiddenException('product_id is required');
}
return $query->where(['product_id' => $product_id]);
}
I don't see an easy way to do the above via a behavior, because the where clause contains only expressions by the time the behavior is executed.

CakePHP's naming conventions on hasMany through (The Join Model)

After struggling with this inconvenience for a couple of weeks now, I've decided to open en now topic here. A topic that can help me, but I'm sure it will help some others with this same problem too!
I wonder how I should name the tables, controllers and models of a hasMany through table (thus with additional fields) and it's coupled tables. I tried to stick on the CakePHP naming conventions as discribed in it's cookbook, but this constantly gave me some "Object not found" errors. For practical reason, I'll show you my problem with a multiple-words named table. Perhaps that could be the reason of the problem? ;)
Situation
I have a fansite of a themepark and as you now, a themepark has many attractions. To ride an attraction, you must have a minimal height. Sometimes, small people can only ride it with an adult companion. But most of the time: you are allowed to ride the attraction because you just are tall enough ;)
Now I want to show the information of a specific attraction on my website. Name, content, photos, and so on. In addition to that information, I want to display my guests if they (or their kids) are tall enough to ride that attraction. It should appear like this way:
0m00 -> 1m00: not allowed
1m00 -> 1m30: allowed with an adult companion
1m30 -> 1m90: allowed
Database
I have two tables that are representing two objects: "attractions" & "attraction_accessibilities". In this case, I'm 100% sure the database names are correct.
Table "attraction_accessibilities" (id - name):
1 - Not allowed
2 - Allowed with an adult companion
3 - Allowed
Table "attractions" (id - name):
1 - Boomerang
2 - Huracan
3 - Los Piratas
4 - El Volador
...
Secondly, I should have another table between "attractions" and "attraction_accessibilities". This table should contain:
an id specific for each record
a link to the id of the "attractions" table (attraction_id)
a link to the id of the "attraction_accessibilities" table
(attraction_accessibility_id)
the additional information like "min-height" and "max-height"
I think I should name that table "attraction_accessibilities_attractions". It's a constriction of the two other tables, and I did it that way because CakePHP proposed it when you're making a HABTM association (http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasandbelongstomany-habtm).
But unfortunately, when I do call it that way, I've never succeeded to link those models in my application together.
Question
Is there anybody who've had the same problem but found a solution for it? If "yes": how should I name my database tables then and also important: how should I name my controller and model .php files?
Many thanks for the one who could help me and some other hopeless programmers ;)
If you use the HABTM relationship with unique set to keepExisting then you can name the table as you like and set the joinTable parameter according to he name you choosed
i.e. in your Attraction model
public $hasAndBelongsToMany = array(
'AttractionAccessibility' =>
array(
'joinTable' => 'attraction_accessibilities_attractions',
)
);
Instead if you're going to use the hasMany through relation then you can name the table as you like. In fact the so called "hasMany through" is just the concatenation of two hasMany relationships
so. If you name your table restrictions then
class Attraction extends AppModel {
public $hasMany = array(
'Restriction'
);
}
class AttractionAccessibility extends AppModel {
public $hasMany = array(
'Restriction'
);
}
class Restrictionextends AppModel {
public $belongsTo = array(
'Attraction ', 'AttractionAccessibility '
);
}

Retrieving data from referenced key table - Laravel-4

The structure of concerning tables is as follows (MySQL):
//Table Name : team
tid PK
team_name (varchar)
//Table Name : fixture
fid PK
home_team_id FK |_ both referenced to 'tid' from 'team' table
away_team_id FK |
My aim is to retrieve the team names. Considering this structure, I think I'll have to retrieve home_team_id and away_team_id and then do something like
Fixture::where('tid','=',$home_team_id)->get();
My question is, is this the correct way to accomplish what I aim to do?
and
should this be done from the controller? (if so, then I'll have to do two queries from same function)
First, rather than having your primary keys be tid and fid, just keep them both as id. This is not only best practice, but will allow you to more easily use Laravel's Eloquent ORM as it by default assumes your primary key column is named id.
Second thing, make sure your table names are in plural form. Although this is not necessary, the example I'm about to give is using Laravel defaults, and Laravel assumes they are in plural form.
Anyway, once you've 'Laravelized' your database, you can use an Eloquent model to setup awesome relationships with very minimal work. Here's what I think you'd want to do.
app/models/Team.php
class Team extends Eloquent {
// Yes, this can be empty. It just needs to be declared.
}
app/models/Fixture.php
class Fixture extends Eloquent {
public function homeTeam()
{
return $this->belongsTo('Team', 'home_team_id');
}
public function awayTeam()
{
return $this->belongsTo('Team', 'away_team_id');
}
}
Above, we created a simple model Team which Laravel will automatically look for in the teams database table.
Second, we created model Fixture which again, Laravel will use the fixtures table for. In this model, we specified two relationships. The belongsTo relationship takes two parameters, what model it is related to, in both cases here they are teams, and what the column name is.
Laravel will automatically take the value in away_team_id and search it against the id column in your teams table.
With just this minimal amount of code, you can then do things like this.
$fixture = Fixture::find(1); // Retrieves the fixture with and id of 1.
$awayTeam = $fixture->awayTeam()->first(); // var_dump this to see what you get.
$homeTeam = $fixutre->homeTeam()->first();
Then you can proceed as normal and access the column names for the tables. So say you have a 'name' column in the teams table. You can echo out the the home team name from the fixture like so.
$fixture = Fixture::find(1); // Get the fixture.
echo $fixture->homeTeam->name;
It's nearly 2AM, so there might be an error or two above, but it should work.
Make sure you check the docs for Eloquent, especially the bits relating to relationships. Remember to name your columns and tables in the way Laravel wants you to. If you don't, there are ways to specify your custom names.
If you want to get even more fancy, you can define the inverse relationship like this on your Team model.
app/models/Team.php
class Team extends Eloquent {
public function fixturesAtHome()
{
return $this->hasMany('Fixture', 'home_team_id');
}
public function fixturesAway()
{
return $this->hasMany('Fixture', 'away_team_id');
}
}
Then to get all of a particular team's home fixtures...
$team = Team::find(1); // Retreive team with id of 1;
$homeFixtures = $team->fixturesAtHome();

How do I add a HABTM record in CakePHP?

I am new to CakePHP and have a fairly basic question.
I have two tables : books and users. books and users have a habtm relationship. I have created the MVC for the above.
Now when a user logs into the system, I want the user to be able to reserve a book (ie an entry in books_users), by looking at the results of the 'index' action. What is the API to be used?
$this->Book->save() does not seem appropriate as we aren't creating a book. We only want an association between an existing book and the logged-in user.
I am trying to avoid, retrieving $this->Book, iterating manually through the sub-array User, creating a new sub-array and saving the whole thing back. I am sure there must be a simpler way.
Adapted from Chuck's answer, unsure why edit was pushed back.
In app/Model/Book.php
class Book extends AppModel {
/************************************************************************
* If you want your multiple assoc. to work you must set unique to *
* false, otherwise when you save an entry it will enforce unique *
* on book ID and subsequently your associations will delete previously *
* saved associations, acting more like "User HasMany Books". *
************************************************************************/
var $hasAndBelongsToMany = array(
'User' => array(
'className' => 'User',
'unique' => false
));
public function addUser($bid, $uid) {
$this->data['User']['id'] = $uid;
$this->data['Book']['id'] = $bid;
$this->save($this->data);
}
}
In app/Controller/BooksController.php (or UsersController)
$this->Book->addUser($bid, $uid);
Fat Models / Skinny Controllers. Allows duplicate entries (you need to constrain limits and check for duplicates, otherwise default behaviour makes HMBTM difficult). Does exactly what you want it to, you just need to supply book and user id.
CakePHP doesn't tend to encourage complex associations, and the reason this is because HMBTM is just a convenience and care should be taken when mixing it with other associations, as per the link provided below, self defined associations are more predictable than HMBTM in CakePHP
http://book.cakephp.org/2.0/en/models/saving-your-data.html#what-to-do-when-habtm-becomes-complicated
You simply need to save a record to Book or User that contains the ids of both and it will insert it into the HABTM table.
$this->data['User']['id'] = {USER_ID};
$this->data['Book']['id'] = {BOOK_ID};
$this->Book->save($this->data);
Look at the HABTM table and you will find the record.
did you bake your application? This (basic) functionality will be provided to you for you to adapt.
In short - take the id of the book and the id of the user. Save it to your books_users table.
id | book_id | user_id
1 1 1
2 2 2
3 2 3
If you have set your associations up correctly, when you request a user, all their books will be returned from the join table.
Your logic will need to deal with - number of books available,if a person can reserve more than one book at once...
http://book.cakephp.org/2.0/en/models/saving-your-data.html#saving-related-model-data-habtm
Has an example.

cakePHP model associations (retrieving data deeper)

I tried to find this everywhere but I cant. Here is my problem:
Lets say I have models associated like this:
Student (has many Memberships)
Membership (belongs to Student and TeacherCourse)
TeacherCourse (belongs to Teacher and Course)
Teacher (has many TeacherCourses)
Course (has many TeacherCourses)
When I use membershipsController to send all membership to the view, I get only for example $membership['Membership']['id'] or $membership['TeacherCourse']['id'] BUT NOT $membership['TeacherCourse']['Teacher']['id']...
Which means I get info on Memberships and TeacherCourse because they are directly associated. My question is how can I also get info about Teachers or Courses directly?
You could increase the model's recursion level to obtain deeper associations (set $this->Membership->recursive to 2 or 3), but in general it's better to use the Containable behavior so you can choose which associations you want to retrieve. For example this would return memberships with associated TeacherCourse and Teacher models:
$membership = $this->Membership->find('all', array(
'contain'=>array(
'TeacherCourse'=>array(
'Teacher'=>array()
),
));
See also the full documentation of the Containable behavior at http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
Scratching my head for a few minutes on this one when I realised you need to add the following to the Model first:
public $actsAs = array('Containable');
Just adding the contain array in the controller may not return the variables you're looking for in the view without the above.

Resources