Unable to specify Model->alias - cakephp

I'm using a custom Class in my program which can connect to multiple tables as and when I need to.
I'm trying to create a new instance of the class, and I can pass in a different table name and alias via the constructor. This works for the table name, but it doesn't set the Model alias as it should in the documentation
Creating an new Object. The Class being instantiated is Lists
$lists = new Lists(null, 'list_musicians', null, null, 'Musician');
Class Constructor
public function __construct($id = false, $table = null, $ds = null, $name = null, $alias) {
parent::__construct($id, $table, $ds, $name, $alias);
$this->virtualFields['full_name'] = sprintf(
'CONCAT(%s.first_name, " ", %s.last_name)', $this->alias, $this->alias
);
//debug($this->alias);die;
}
This will connect to the table named 'list_musicians' or whatever table name I pass in, but the $alias field does not get assigned to $this->alias
If I reassign the alias manually, the virtual fields are not included in the same array as the result, as specified in the documentation. The alias is always Lists
How can I set the model alias via the constructor?

I'm using a custom Class in my program which can connect to multiple
tables as and when I need to.
Honestly that's a pretty bad idea in the most cases. If you already read the MVC chapter from of the book read it again, if not read it now.
And you're wrong, the constructor of the model class doesn't know a 4th argument for the alias, check your own link and read it again.
public function __construct($id = false, $table = null, $ds = null)
However, if you want to insist on bad practice you can always instantiate Model objects by using ClassRegistry::init().
Examples Simple Use: Get a Post model instance
ClassRegistry::init('Post');
Expanded: array('class' => 'ClassName', 'alias' =>
'AliasNameStoredInTheRegistry');
Model Classes can accept optional array('id' => $id, 'table' =>
$table, 'ds' => $ds, 'alias' => $alias);
Just use Model as class name and provide whatever table you want.

Solution to this is just to create model associations directly to the db tables. Use the table name as the classname and voila
'Religion' => array(
'className' => 'list_religions',
'foreignKey' => 'religion_id',
'conditions' => '',
'fields' => '',
'order' => ''
)

Related

Fetch default value from schema [duplicate]

How do you set a default value for a field in a model?
EDIT:
I have tried the method using _schema as suggested, but the default value is not being used.
public $_schema = array(
'newsletter' => array(
'default' => 1
),
);
you should always try to set default values from the controller:
http://www.dereuromark.de/tag/default-values/
It would be better to set the default value in the database? I don't really see why you would want to do it CakePHP side...
As the above suggestions do not work for me, so I have found my own. The answer is very similar to the written above, but with one little correction. (works with CakePHP 2.6.1)
The default value can be set in the controller in add function ("request" is needed).
$this->request->data['Country']['hasFlag'] = 1;
Full code example:
public function add() {
if ($this->request->is('post')) {
$this->Country->create();
if ($this->Country->save($this->request->data)) {
...
} else {
...
}
}
$this->request->data['Country']['hasFlag'] = 1; // default value passing to the view
}
Some philosophy:
1) Why this is needed - If we have a boolean attribute in the database, the newly created object in Cakephp does not take into the account default values from database. And if we leave checkbox unchecked in the Add form of the new object and submit it to the database - it means this attribute value is false (not value not set)
2) Is this an ideal place to set default value? - No, this is not an ideal place, as all information about the Object and its data must be in the Model, but I haven't managed to assign default value in the model. Even using _schema variable or create function.
You can set the value in database and get it managed in schema, e.g.:
public $items = array(
'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => 10, 'unsigned' => false, 'key' => 'primary'),
'quantity' => array('type' => 'decimal', 'null' => false, 'default' => '1.00', 'length' => '12,2', 'unsigned' => false),
// ^^^^^^^^^^^^^^^^^^^
'indexes' => array(
'PRIMARY' => array('column' => 'id', 'unique' => 1),
),
'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_spanish_ci', 'engine' => 'InnoDB')
);
This default value can be read later in model or controller though the model's schema property:
// Controller example
$itemSchema = $this->Item->schema();
$defaultQuantity = $itemSchema['quantity']['default'];
// ... or:
$quantityInfo = $this->Item->schema('quantity');
$defaultQuantity = $quantityInfo['default'];
In recent PHP versions it can be a one-liner:
$defaultQuantity = $this->Item->schema('quantity')['default'];
This works in Cake/2.5 with MySQL adapter (no idea of other scenarios).
databases get big, so you cannot remember all those defaults you've set. Let's keep it simple:
You set default values in the controller (it's easy to read the code, if default values for certain actions are set at the beginning as class properties).
For example:
class UsersController extends AppController {
private $registerDefaults = array(
'group_id' => '1'
);
public function register() {
if ($this->request->is('post')) {
/*
* This is where you set default value
* Here's what I do for default group that user should be assigned to
*/
$this->request->data['User']['group_id'] = $this->registerDefaults['group_id'];
if ($this->User->save($this->request->data)) {
$this->Session->setFlash(__('You have been successfully registered.'));
return $this->redirect(array('action' => 'index'));
}
$this->Session->setFlash(__('We're unable register this user.'));
}
}
}
You can't always remember default values set in the database if you've got about 60-80 tables with complicated relations.
AND
My advice is that you don't set defaults that depend on your current settings, be more flexible: create configuration table or set defaults in AppController in order to find it wth a blink of an eye.

How should I declare my tree behavior in my model to preserve scope in CakePHP 2.5.x?

I'll be straightforward: I want to manage multiple (Link) trees according to their respective menu_id. As long as there is only one tree: no problem. Things get messed up when I start another tree in my link model with a different menu id.
I whish to be able to add, edit, remove, moveUp or moveDown while preserving the scope (menu_id).
This part of the documentation is unclear to me :
http://api.cakephp.org/2.5/source-class-TreeBehavior.html#41-49
Here my Link model.
<?php
App::uses('AppModel', 'Model');
class Link extends AppModel {
public $name = 'Link';
public $displayField = 'title';
public $actsAs = array('Tree' => array(
'parent' => 'parent_id',
'left' => 'lft',
'right' => 'rght',
'scope' => "WHAT-SHOULD-I-PLACE-HERE??",
));
public $belongsTo = array(
'Menu' => array(
'className' => 'Menu',
'foreignKey' => 'menu_id',
)
);
}
And my Menu model.
<?php
App::uses('AppModel', 'Model');
class Menu extends AppModel {
public $displayField = 'title';
public $hasMany = array(
'Link' => array(
'className' => 'Link',
'foreignKey' => 'menu_id',
'dependent' => false,
)
);
}
Thanks to BadHorsie I finally came up to understand that is it pointless to declare the scope in the model's behavior settings.
Instead you (currently I) need to attach the behavior on the fly, with the required scope before any action (add, edit, move up, move down etc) for this to work.
Now, what I need to make sure is that a link always has a menu_id.
And when someone edits a link, the parent_id dropdown must be repopulated according to the menu_id dropdown (javascript needed here and more validation rules in the model to ensure integrity).
The solution was to create a new function in the Link model. it's goal is to preserve the scope when any modification is made to the tree (move up, move down, remove from tree, delete, etc).
public function preserveScope($menuId) {
$this->Behaviors->attach('Tree', array(
'scope' => array(
'Link.menu_id' => $menuId
),
));
return true;
}
The scope is basically a SQL condition (in Cake format).
So you probably need to set the scope to the menu ID that you want.
'scope' => array(
'Link.menu_id' => 5
);
However, you probably don't know which ID yet when trying to set up the array on the class definition, so you might have to do it on the fly.
$this->Link->Behaviors->attach('Tree', array(
'scope' => array(
'Link.menu_id' => $id // You need to decide how to get this ID
),
));
I don't know when you would need to do this though. It's up to you to decide when to attach the behavior.
Edit: If the moveUp/moveDown methods are not working correctly, perhaps the scope field you are using is not correct?

How do I load associated models and save related data in CakePHP?

I am setting up a user/group system that allows users to send requests to join a group.
I can't seem to load the associated model and add a row. It's really really really difficult to resist the urge to just use $this->query() and be done with it... but I'm trying to learn the Cake conventions and do things the right way.
In my group model's function for handling group join requests:
$this->loadModel('GroupRequest');
$this->data = array('GroupRequest' =>
array('user_id' => $uid, 'group_id' => $gid));
if($this->GroupRequest->save($this->data)){ echo 'save success'; }
else { echo 'save fail'; }
Here are the errors I get when I run this:
Warning (512): SQL Error: 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'loadModel' at line 1 [CORE/cake/libs/model/datasources/dbo_source.php, line 684]
Query: loadModel
Notice (8): Undefined property: Group::$GroupRequest [APP/models/group.php, line 124]
Fatal error: Call to a member function save() on a non-object in /home/wxworksmat/sspot3/app/models/group.php on line 124
I also tried using App::import:
App::import('Model','GroupRequest');
I don't get any SQL errors importing the Model this way, but it still doesn't work. I get the following error on the save() or create() call:
Fatal error: Call to a member function save() on a non-object in /home/wxworksmat/sspot3/app/models/group.php on line 124
You are confusing controller and model methods
$this->loadModel()
is a controller method and can only be used there.
You should always use
$this->ModelName = ClassRegistry::init('ModelName');
everywhere else
I might be wrong, and please excuse me if I'm wrong, but it looks like you don't understand the concept of the framework very well. It is difficult to answer your question without giving you a complete tutorial.
This said, everything relies on model associations. If it's done correctly, things are getting easy. You should read:
Associations: Linking Models Together
Once you have your models correctly linked, you will be able to save the primary model, as well as the related model, very easily.
Saving Related Model Data (hasOne, hasMany, belongsTo)
As I understand, you are trying to do this from inside a model?
class GroupRequest extends AppModel {
public function associate($user, $group) {
$data["GroupRequest"] = array("user_id" => $user, "group_id" => $group);
$this->save($data);
}
}
Then in your Controller (assuming group_requests_controller)
$this->GroupRequest->associate($user, $group);
If you're calling this from another controller you would loadModel first
$this->loadModel("GroupRequests");
$this->GroupRequest->associate($user, $group);
However, if you're doing all of this from within GroupRequests controller you should be able to save directly, without making a separate method for it
public function add() {
$this->GroupRequest->create();
$this->GroupRequest->save($this->data); #for < 2.0
}
Your view should be something like
<?php
echo $this->Form->create("GroupRequest");
echo $this->Form->input("user_id");
echo $this->Form->input("group_id");
echo $this->Form->end("Submit");
?>
The problem I had was that I didn't have the correct model association declarations at the top of my model.
Now I have:
group.php
var $hasMany = 'GroupRequest';
group_request.php
var $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Group' => array(
'className' => 'Group',
'foreignKey' => 'group_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
public function new_request($user, $group) {
$data["GroupRequest"] = array("user_id" => $user, "group_id" => $group, 'status' => 'pending');
if($this->save($data)){ return true;} else {return false;}
}
Now because everything is set up CORRECTLY... I can do this in my group.php model:
$this->GroupRequest->new_request($uid,$gid)
As an added bonus, because the assocations are populating properly, when I do $this->find in my group or user model, now all the related GroupRequest entries show up. Bonus data FTW.

CakePHP - Set Default Field Values in the Model

How do you set a default value for a field in a model?
EDIT:
I have tried the method using _schema as suggested, but the default value is not being used.
public $_schema = array(
'newsletter' => array(
'default' => 1
),
);
you should always try to set default values from the controller:
http://www.dereuromark.de/tag/default-values/
It would be better to set the default value in the database? I don't really see why you would want to do it CakePHP side...
As the above suggestions do not work for me, so I have found my own. The answer is very similar to the written above, but with one little correction. (works with CakePHP 2.6.1)
The default value can be set in the controller in add function ("request" is needed).
$this->request->data['Country']['hasFlag'] = 1;
Full code example:
public function add() {
if ($this->request->is('post')) {
$this->Country->create();
if ($this->Country->save($this->request->data)) {
...
} else {
...
}
}
$this->request->data['Country']['hasFlag'] = 1; // default value passing to the view
}
Some philosophy:
1) Why this is needed - If we have a boolean attribute in the database, the newly created object in Cakephp does not take into the account default values from database. And if we leave checkbox unchecked in the Add form of the new object and submit it to the database - it means this attribute value is false (not value not set)
2) Is this an ideal place to set default value? - No, this is not an ideal place, as all information about the Object and its data must be in the Model, but I haven't managed to assign default value in the model. Even using _schema variable or create function.
You can set the value in database and get it managed in schema, e.g.:
public $items = array(
'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => 10, 'unsigned' => false, 'key' => 'primary'),
'quantity' => array('type' => 'decimal', 'null' => false, 'default' => '1.00', 'length' => '12,2', 'unsigned' => false),
// ^^^^^^^^^^^^^^^^^^^
'indexes' => array(
'PRIMARY' => array('column' => 'id', 'unique' => 1),
),
'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_spanish_ci', 'engine' => 'InnoDB')
);
This default value can be read later in model or controller though the model's schema property:
// Controller example
$itemSchema = $this->Item->schema();
$defaultQuantity = $itemSchema['quantity']['default'];
// ... or:
$quantityInfo = $this->Item->schema('quantity');
$defaultQuantity = $quantityInfo['default'];
In recent PHP versions it can be a one-liner:
$defaultQuantity = $this->Item->schema('quantity')['default'];
This works in Cake/2.5 with MySQL adapter (no idea of other scenarios).
databases get big, so you cannot remember all those defaults you've set. Let's keep it simple:
You set default values in the controller (it's easy to read the code, if default values for certain actions are set at the beginning as class properties).
For example:
class UsersController extends AppController {
private $registerDefaults = array(
'group_id' => '1'
);
public function register() {
if ($this->request->is('post')) {
/*
* This is where you set default value
* Here's what I do for default group that user should be assigned to
*/
$this->request->data['User']['group_id'] = $this->registerDefaults['group_id'];
if ($this->User->save($this->request->data)) {
$this->Session->setFlash(__('You have been successfully registered.'));
return $this->redirect(array('action' => 'index'));
}
$this->Session->setFlash(__('We're unable register this user.'));
}
}
}
You can't always remember default values set in the database if you've got about 60-80 tables with complicated relations.
AND
My advice is that you don't set defaults that depend on your current settings, be more flexible: create configuration table or set defaults in AppController in order to find it wth a blink of an eye.

Database agnostic virtual fields in CakePHP

In CakePHP 1.3 there is a feature for virtual fields but it's coupled with the database that you are using. For example:
var $virtualFields = array(
'full_name' => 'CONCAT(User.first_name, " ", User.last_name)'
);
This would work for MySQL but not for MS SqlServer. Is there a way to make this database agnostic?
I'm still in the middle of developing an application and still not sure what database we'll be using in production. That's why I want to keep all the database access as agnostic as possible.
You could dimension your Model::virtualFields property such that it had rules for each database:
var $virtualFields = array(
'mysql' => array(
'display_name' => 'CONCAT(User.name, " (", User.team, ")")',
),
'postgres' => array(
'display_name' => 'PgConcatStuff(...)',
),
'mssql' => array(
'display_name' => 'MsConcatStuff(...)',
),
);
The trick then is to catch the above property and manipulate it on-the-fly so Cake never realises:
class AppModel extends Model {
function beforeFind($queryData) {
$ds = $this->getDataSource();
$db = $ds->config['driver'];
$this->virtualFields = $this->virtualFields[$db];
return parent::beforeFind($queryData);
}
The above code was tested with a simple find on a single model. More testing may be needed to check for edge-cases involving related models. The finished functionality belongs in a behavior. :)

Resources