Model Table CakePHP 3 using existing table - cakephp

Hi need your help to understand some feature in CakePHP.
I have a SQL Table : user.
I generate with bake the Model : UserTable.
In the action home() of my UsersController, i have this :
$t_Results = TableRegistry::get('User')->findByLogin('jdupont')->execute()->fetchAll('assoc');
debug($t_Results);
The query is generated by Cake and this code works well.
My question are :
Must i create the function findByLogin inside the Model or not ?
Is my code correct ?
Thanks for the help ;)

Yes you can create a findByLogin in your model but you don't have to.
Your code works but doesn't respect conventions.
In CakePHP 3
SQL tables are singular lowercase,
Table files has upper first letter and plural suffixed by Table,
Controllers are plural first letter upper and suffixed by Controller.
If you follow these conventions in your controller you can do this:
$t_Results = $this->Users->findByLogin('jdupont')->execute()->fetchAll('assoc');
debug($t_Results);
You don't have to use ->execute(). Query objects are lazily evaluated, execute will be called when you will use the request.

One of the quickest ways for you to check if your code is correct is to actually run it and see if it returns what you expect.
findByLogin() is a Cake dynamic finder so you don't need to define this method as Cake dynamically does this for you. You can prefix any camel-cased column name with findBy to query a table using that column.
You can use it like this:-
$t_Results = $this->Users->findByLogin('jdupont')->first();

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 3.3 tables dedicated for different data based on the selected language

I have a non-standard question to CakePHP 3.3. Let's imagine that in my database I have two tables: A and B (both are identical, first is dedicated for data in the first language, second is dedicated for data in the second language).
I correctly coded the whole website for table A (table B is not yet in use). Additionally, I implemented the .po files mechanizm to switch the language of the interface. The language of the inteface switches correctly.
How can I easily plug the table B - I do not want to make IF-ELSE statements in all cases because the website is getting big, and there are many operations in table A already included. Is there a possibility to somehow make a simple mapping that table A equals table B if language pl_PL is selected to en_US (through .po files)?
The most simple option that comes to my mind would be to inject the current locale into your existing table class, and have it set the database table name accordingly.
Let's assume your existing table class would be called SomeSharedTable, this could look something along the lines of:
// ...
class SomeSharedTable extends Table
{
public function initialize(array $config)
{
if (!isset($config['locale'])) {
throw new \InvalidArgumentException('The `locale` config key is missing');
}
$table = 'en_table';
if ($config['locale'] === 'pl_PL') {
$table = 'pl_table';
}
$this->table($table);
// ...
}
// ...
}
And before your appplication code involves the model layer, and after it sets the locale of course (that might for example be in your bootstrap), configure the alias that you're using throughout your application (for this example we assume that the alias matches the table name):
\Cake\ORM\TableRegistry::config('SomeShared', [
'locale' => \Cake\I18n\I18n::locale()
]);
Given that it's possible that the locale might not make it into the class for whatever reason, you should implement some safety measures, I've just added that basic isset() check for example purposes. Given that a wrongly configured table class could cause quite some problems, you probably want to add some checks that are a little more sophisticated.

Laravel get value of column from relation through pivot on load() after all()

I have a problem retrieving values of a column from relations in Laravel.
I have a User - Model. This model has relation to a table btw. a model named Userhobbies.
For now we have:
User ::: hasMany >>> Userhobbies
Now with User::all()->load('hobbies') I'm getting right results like
{"id":"1","username":"jdoe","first_name":"Joe","last_name":"Doe","birth":"
1992-04-11","picture_id":"f3dca65323e876026b409b9ba3d49c56","hobbies":
[{"hobby_id":"1","user_id":"1"},{"hobby_id":"2","user_id":"1"},
{"hobby_id":"3","user_id":"1"},{"hobby_id":"4","user_id":"1"}]}
As you can see Userhobbies contains only primary-key relations between hobby - table (Hobby Model) and user - table (User Model).
(Hobby model also has hasMany relation to Userhobbies)
My question now is - how to retrieve all hobby-names (from hobby - table) in my call over (after load('hobbies') ) and is it possible without writting a lot of code?
For better understanding of my idea the result which I want to retrieve:
{"id":"1","username":"jdoe","first_name":"Joe","last_name":"Doe","birth":"
1992-04-11","picture_id":"f3dca65323e876026b409b9ba3d49c56","hobbies":
["golf", "cards", "games", "football"]}
EDIT:
If I try following (I tried with belongsToMany in User and Hobby):
User::with('hobbies')->get()->first()
And I'm getting the whole values from the hobbies - table:
{user-specific data ...
hobbies:[{"id":"1","name":"golf","created_at":"2015-04-07
14:15:02","updated_at":"2015-04-07 14:15:02","pivot":
{"user_id":"1","hobby_id":"1"}},
{"id":"2","name":"cards","created_at":"2015-04-07
14:15:02","updated_at":"2015-04-07 14:15:02","pivot":
{"user_id":"1","hobby_id":"2"}},
{"id":"3","name":"games","created_at":"2015-04-07
14:15:02","updated_at":"2015-04-07 14:15:02","pivot":
{"user_id":"1","hobby_id":"3"}},
{"id":"4","name":"football","created_at":"2015-04-07
14:15:02","updated_at":"2015-04-07 14:15:02","pivot":
{"user_id":"1","hobby_id":"4"}}]}
Same try with ->load('hobbies'). I really don't know how to go on.
To explain it a bit more what I need one could imagine such query as follows:
User::all(['id', 'name'])->load(array('hobbies.id','hobbies.name'))->get();
From my knowledge, I know that it's possible to use a closure to set constraints on the query that performs the load, like so:
User::all()->load(['hobbies' => function($query)
{
$query->select('id', 'name');
}]);
By doing it, when you cast it to array, it will produce a result near to what you want. You can even add 'pivot' to your $hidden property on your Hobby model to hide this information.

cakephp setting select options and values at Model

In my database model, my attribute is set as type INT.
On the front end, I want to display a select field with representative values for the respective Integer values.
eg: [1 = Home, 2 = About]
I am currently using an external plugin for the administrating content, and the select values only allows integer. So my idea is to achieve this at respective Model. Is it possible?
Genarally yes.
You should be able to attach results of Model->find('list') to select field. Of course your model should have name or title fields for description values (Home, About).
Sounds like the kind of enum representation as I always use.
Try this solution:
http://www.dereuromark.de/2010/06/24/static-enums-or-semihardcoded-attributes/
I basically uses an array matching to resolve those ints into strings in a clean way - using the model. can be the whole array for select fields or just the specific string for output in the view/index.
Its also fully form and bake-template capable.
If you name the field "attribute" in your table, and name the method "attributes()" you can easily have "cake bake" to bake this via custom templates.

Rename field using Objectify and Google App Engine

I am trying a case where we changed a field name in our entity. we have something like this for example
class Person {
String name; //The original declaration was "String fullName"
}
According to objectify you have to use annonation #AutoLoad(""). This is ok and it works as Google Datastore doesn't delete the data Actually but it makes a new field so this annotation is like a mapping between the old and the new field. No problem when you are reading the whole table.
The problem arises when you apply a filter on your query (Suppose you made 5 objects with old name and 5 with new name). The result of your query depends on whether you used the old variable name or the new one (returns back only 5 but never the 10). It won't fetch both of them and map them. Any Suggestions for this problem? I hope i explained it in a clear way.
Thanks in advance
The simplest straight forward solution. fetch all data with the annonation "AutoLoad()". Then store them again. In this way they will be saved as the new field. The old one doesn't exist anymore or at least it doesn't contain any data anymore. It is like migrating the data from the old name to the new name. Anyone has better suggestions ?
If you've changed the name of your field, you need to load and re-put all your data (using the mapreduce API would be one option here). There's no magic way around this - the data you've stored exists with two different names on disk.
You can use #OldName
http://www.mail-archive.com/google-appengine-java#googlegroups.com/msg05586.html

Resources