virtual fields in associated view - php

I'm having $virtualFields of users set in the model
public $virtualFields = array(
'fullname' => 'CONCAT(user.firstname, " ", user.lastname)',
[...]
);
I want to access this field in a associated view, but I can load users data like firstname in the other view, but not the combined field fullname.
Do I have to add something into the controller to get this field.

You probably didnt read the last chapter of the book page here:
http://book.cakephp.org/2.0/en/models/virtual-fields.html#limitations-of-virtualfields
Otherwise you would have seen, how do overcome the limitation of virtual fields here:
$this->virtualFields['fullname'] = $this->User->virtualFields['fullname'];

Related

CakePHP 4 - how to validate forms that need to save data to multiple tables

Apologies if this has been asked before. All of the examples I can find are old or apply to legacy versions of CakePHP, e.g. cakephp: saving to multiple models using one form is 7 years old.
I have an application in CakePHP 4.1.6. Two of the tables in the database are called tbl_users and tbl_orgs ("orgs" in this case means "Organisations").
When I add an Organisation I also want to create a User who is the main contact within the Organisation. This involves saving to both the tbl_orgs and tbl_users tables when the form is submitted.
The problem I'm experiencing is how to get the form working in a way where it will run the validation rules for both tbl_users and tbl_orgs when submitted.
This is how our application is currently structured:
There is a Controller method called add() in src/Controller/TblOrgsController.php. This was generated by bake and was initially used to insert a new Organisation into the tbl_orgs table. At this point it didn't do anything in terms of tbl_users however it worked in terms of saving a new Organisation and running the appropriate validation rules.
One validation rule is that every companyname record in tbl_orgs must be unique. If you try to insert more than 1 company with the name "My Company Limited" it would give the validation error "This company name already exists":
// src/Model/Table/TblOrgsTable.php
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add(
$rules->isUnique(['companyname']),
[
'errorField' => 'companyname',
'message' => 'This company name already exists',
]
);
return $rules;
}
Whilst the above applies to TblOrgs we also have an buildRules() in TblUsers which applies similar logic on an email field to make sure that all email addresses are unique per user.
In the add() Controller method we start by specifying a new empty entity for TblOrgs:
// src/Controller/TblOrgsController.php
public function add()
{
$org = $this->TblOrgs->newEmptyEntity();
// ...
$this->set(compact('org'));
}
When the form is created we pass $org:
// templates/TblOrgs/add.php
<?= $this->Form->create($org) ?>
<?= $this->Form->control('companyname') ?>
<?= $this->Form->end() ?>
When the TblOrgs fields are rendered by the browser we can inspect the HTML and see these are obeying the corresponding Model. This is clear because of things such as required="required" and maxlength="100" which correspond to the fact that field is not allowed to be empty and is a VARCHAR(100) field in the database:
<input type="text" name="companyname" required="required" id="companyname" maxlength="100">
It also works in terms of the rules specified in buildRules for TblOrgs. For example if I enter the same company name twice it shows the appropriate error in-line:
I then tried to introduce fields for TblUsers. I prefixed the form fields with dot notation, e.g. this is intended to correspond to tbl_users.email input field:
<?= $this->Form->control('TblUser.email') ?>
When inspecting the HTML it doesn't do the equivalent as for TblOrgs. For example things like maxlength or required are not present. It effectively isn't aware of TblUsers. I understand that $org in my Controller method is specifying a new entity for TblOrgs and not TblUsers. I reviewed the CakePHP documentation on Saving With Associations which says
The save() method is also able to create new records for associations
However, in the documentation the example it gives:
$firstComment = $articlesTable->Comments->newEmptyEntity();
// ...
$tag2 = $articlesTable->Tags->newEmptyEntity();
In this case Tags is a different Model to Comments but newEmtpyEntity() works for both. With this in mind I adapted my add() method to become:
$org = $this->TblOrgs->TblUsers->newEmptyEntity();
But this now gives an Entity for TblUsers. It seems you can have either one or the other, but not both.
The reason this doesn't work for my use-case is that I can either run my Validation Rules for TblOrgs (but not TblUsers) or vice-versa.
How do you set this up in a way where it will run the validation rules for both Models? It doesn't seem to be an unreasonable requirement that a form may need to save data to multiple tables and you'd want the validation rules for each of them to run. I get the impression from the documentation that it is possible, but it's unclear how.
For reference there is an appropriate relationship between the two tables:
// src/Model/Table/TblOrgsTable.php
public function initialize(array $config): void
{
$this->hasMany('TblUsers', [
'foreignKey' => 'o_id',
'joinType' => 'INNER',
]);
}
and
// src/Model/Table/TblUsersTable.php
public function initialize(array $config): void
{
$this->belongsTo('TblOrgs', [
'foreignKey' => 'o_id',
'joinType' => 'INNER',
]);
}
Okay, lots of confusion to clear up here. :-) My assumption here, based on what you've written, is that you're trying to use a single form to add a new organization, and the first user in it, and then maybe later you'll add more users to the org.
First, $this->TblOrgs->TblUsers is your users table object, so when you use
$org = $this->TblOrgs->TblUsers->newEmptyEntity();
what you're doing is creating a new user entity. The fact that you got to that table object through the orgs table, and that you're calling it $org doesn't change that. It doesn't somehow magically create a blank org entity with a blank user entity in it. But you don't need that entity structure here at all here, just the empty org entity. Go back to simply:
$org = $this->TblOrgs->newEmptyEntity();
Now, in your form, you'll want something like this:
<?= $this->Form->create($org) ?>
<?= $this->Form->control('companyname') ?>
<?= $this->Form->control('tbl_users.0.email') ?>
<?= $this->Form->end() ?>
The field is called tbl_users.0.email because:
The table name gets converted to lower case underscore format.
It's a hasMany relation from orgs to users, so it's expecting an array of users; we have to give a numeric index into that array, and 0 is a great place to start. If you were going to add a second user at the same time, the field for that would be tbl_users.1.email.
Note: A great way to figure out what format the form helper is expecting you to create your field names in is to read an existing set of records from the database (in this case, an org and its users), and then just dump that data, with something like debug($org);. You'll see that $org has a property called tbl_users, which is an array, and that will point straight to this structure I've described above.
With the fields set up like this, you should be able to patch the resulting data directly into your $org entity in your controller, and save that without any other work. The patch will created the entire structure, with a entity of class TblOrg, with a tbl_users property which is an array containing a single entity of class TblUser, and validation will have been done on both of them. (At least it should; you can use debug($org); as mentioned above to confirm it.) And when you save this entity, it will first save the TblOrg entity, then add that new ID into the TblUser entity before saving it, as well as checking the rules for both and making sure that nothing gets saved to the database if it can't all be saved. That all happens automatically for you with the single save call!
If your association was a hasOne or belongsTo relation (for example if you were adding a new user and also the org that they're in, instead of the other way around), you could dump a sample $user, and see that it has a property called tbl_org which is just a straight-up entity, not an array of entities, and note that tbl_org is now singular, because it's just one entity instead of a bunch. In this case, the field name to use would be tbl_org.companyname, no array index in there at all.

CakePHP: Removing Redundancy In Database Tables

I am working on models using cakephp. I have two models, the post model; which contains a $hasMany relationship with the tags model. I want the tags name value to be a unique. This is to remove any data redundancy. I want to make it searchable via a AJAX like search box for the tags. I need to be able to automatically create the tags on the fly, but only if they have not already been created. How do i achieve this with CakePHP 2.6.1?
Example:
I have created two tables:
tags:
id name post_id
posts:
id title url content
In order to make sure that the name value of any tag is unique, simply add a validation rule to the Tag model for the name field. There is a specific isUnique validation rule. In your app/Model/Tag.php file, add this:
public $validate = array(
'name' => array(
'rule' => 'isUnique',
'message' => 'This tag already exists.'
)
);
That will cause any save operation on the Tag model with a duplicate tag name to fail with the error message you set in the model.
To make it ignore any duplicates, slightly change the way your save works. Add something like this to your Controller logic:
foreach ($this->request->data['Tags'] as $tagData) {
$this->Tag->set($tagData);
if ($this->Tag->validates()) {
$this->Tag->create();
$this->Tag->save($tagData);
}
}

laravel how to specifiy the input columns

I have a form in html that has an input with name restaurantName, but my column in the database is name. I am not able to use this in the controller
Restaurant::create(Input::only('restaurantName'));
Because there is no column with restaurantName so my question is how to tell laravel that restaurantName is maped to name ?
many thanks
Edit
the html form has these fields:
restaurantName
website
username
password
mobileNumber
firstName
lastName
The database has two tables which are Admin and Restaurant, each restaurant has one admin and the admin is one for each restaurant, so it is one to one relationship
when I submit the form I do the following:
$input = Input::all();
$admin = Admin::create(Input::only('username', 'password', 'mobileNumber', 'firstName', 'lastName'));
$data = ['name' , Input::get('restaurantName'), 'website' => Input::get('website')];
$restaurant = new Restaurant($data);
$admin->restaurant()->save($restaurant);
the admin row is inserted to the database so the problem starts with the $data line code.
the exception is:
BadMethodCallException
Call to undefined method Illuminate\Database\Query\Builder::save()
Firstly, you should never just blindly pass user input to models, and secondly, this particular problem has absolutely nothing to do with laravel.
Your problem can be solved with basic PHP. Simply, create your own array of input.
$data = [
'field' => Input::get('field'),
'name' => Input::get('restaurantName')
];
There seems to be a grave misconception that Laravel is its own independant system, it is not. It is literally a collection of PHP code, it doesn't do anything that can't be done with PHP and it doesn't prevent you from doing anything you can normally do with PHP.

CakePHP form select value as table.id, select option text as table.textfield

I'm new to Cake. I've gotten some things set up, relevantly a "users" and a "profiles" table, controller, model and view. In my profiles/add view, I'm attempting to output a select input with the value being the user.id and the option text being the user.username. Ultimately, the value user.id will be saved in profile.user_id.
I have read through a lot of documentation, and successfully set up the select to output the options from the user tables, but it uses the user.id as both the value and the option text.
Controller:
$this->set('users', $this->Profile->User->find('list'));
View:
echo $this->Form->input('user_id');
It seems like this sort of thing would be routine, so feel free to answer with just a link to the documentation if I've overlooked it completely.
Thanks!
successfully set up the select to output the options from the user tables, but it uses the user.id as both the value and the option text.
find('list') will by default use the primary key as the key, and the model's displayField as the value.
If a model does not have an explicit displayField but has a field named title or name Cake will automatically pick this field, otherwise it will not guess and simply use the primary-key field - which is what's happening in the question.
You can however simply specify which fields you want in the results of your find('list') call. Therefore either:
Set the model displayField
class User extends AppModel {
public $displayField = 'username';
}
and then use:
$users = $this->User->find('list');
Explicitly choose the fields
$users = $this->User->find('list', array(
'fields' => array('id', 'username')
));
In both cases - the result will be e.g.:
array(
1 => 'firstuser',
...
76 => 'AD7six'
)

form->select('group_id') only shows id and not the group in Cakephp

I'm building a application where admins can add multiple users to groups. In the Add action of the UserController i've:
$this->set('groups', $this->User->Group->find('list'));
The output should be a selectbox with the different group currently existing in the groups table.
Groups table:
'id' 'group' 'comments'
Model (user):
public $belongsTo = array('Group');
UserController:
$this->set('groups', $this->User->Group->find('list'));
View (Users/add)
echo $this->Form->input('Group_id');
What am I doing wrong?
In the Group class add this line
var $displayField = 'group';
This will tell the Cake that, for this class, it should treat group as if it was the name field in a standard model and find('list') will produce the results you want.
It looks correct, I assume you have group_id field in users table. You may want to try couple of things.
Change the case, e.g. Group_id to group_id
Change the display of select box as follow
echo $this->Form->input('group_id', $groups);
the case of the chars is pretty important in programming.
not only for files but also for class names and in your case the field names.
otherwise your code will not work correctly:
echo $this->Form->input('group_id');
should fix things.
If I'm not wrong, the find('list') is looking for id and name, so you have to specify the fields in you case because you are missing the name field
I hope that helps

Categories