I give. Been searching and trying different stuff for hours.
Using Cakephp 2.3.5.
I'll get straight to it.
I want to use a session variable in my Category model, but it doesn't like it when I try any of the following...
$this->Session->read('market_id')
CakeSession::read('market_id')
SessionHelper::read('market_id')
Here is the model snippet where I'm trying to use it...
public $hasAndBelongsToMany = array(
'Vendor' => array(
'className' => 'Vendor',
'joinTable' => 'categories_vendors',
'foreignKey' => 'category_id',
'associationForeignKey' => 'vendor_id',
'unique' => 'keepExisting',
'conditions' => array( 'market_id' => ???? )
)
);
I'm stuck like a wheelbarrow in the mud. I've read countless opinions of why I shouldn't use session data in the model, but this makes perfect sense to me since it will never be called without this value set, and it should never return anything other than the vendors with that market_id. But that value does change.
I'm completely guilty of doing everything I can to avoid messing with my models. The whole skinny controller idea... yea... nice thought, but I just haven't figured it out yet. And so it goes. The first time I try to modify a model... I can't figure out how.
I've read countless opinions of why I shouldn't use session data in the model
That's correct. It will cause tight coupling as well and make it harder to test. And it's not only valid for models but everything. Don't create tight coupled code.
It is better to pass data and objects around. For example:
// Model Method
public function getSomething($someSessionValue) { /*...*/ }
// Controller
$this->SomeModel->getSomething($this->Session->read('something'));
If you need your data in a lot methods you can set it:
public function setSomeModelProperty($value) {
$this->_someProperty = value; // _ because it should be protected, the _ is the convention for that in CakePHP
}
However, I personally would very like go for passing the data to each method. But it depends on the whole scenario.
And you can't do this any way because it will cause a syntax error. You can't use variables or properties in the property declaration.
'conditions' => array( 'market_id' => ???? )
So bind it later by a method like
public function bindVendors($marketId) {
$this->bindModel(/*...*/);
}
when needed.
Or even better, simply use the conditions only when needed and declare them in your find options:
[
'contain' => [
'Vendor' => [
'conditions' => /* conditions here */
]
]
]
Related
Recently getting back up-to-speed with unit testing. Been scouring the net for a good mock PDO SQL class for interactions/data but can't seem to one that doesn't require you to parse what the raw sql is into the mock obj during the test script.
This might be okay for some, but IMO it takes too much away from the test script (I'm trying to minimize complications to help sell the concept to my team at work)
Ideally I'd expect the test to look more like this:
public static function setUpBeforeClass()
{
// Use PDOMock version in replace of all PDO calls
PDOMock::initMockMode();
// Potentially drive data provider from a file
$data = [
[
'id' => 1,
'total' => 1,
],
[
'id' => 2,
'total' => 1,
],
[
'id' => 3,
'total' => 10,
],
];
PDOMock::setTableData('db.example_table', $data);
}
public function testSqlMock()
{
// Presumming the sumTableValues() method runs a simple sum query over the db.example_table using the regular \PDO class
$total = TestClass::sumTableValues();
$this->assertEquals(12, $total); // assertion passes due to PDOMock class using set data over live mysql data
}
It doesn't really need to have the fancy PDOMock::initMockMode() logic either as I can compromise by adding a setMockMode() in my sqlclass to just use the mocked version instead of \PDO
Hope this makes sense; any ideas/suggestions are greatly appreciated!
For a Web-Application with many Database related events I want to build a Changelog. That should log what users have done, so its a Userlog too.
The App is huge, has a complex role based user access system and there will be hundreds of different events (changes) that may occur.
This all should be Database-Driven, in PHP and needs at least a View to search the Logs.
But in short, I have totally no idea how to design that all and need some tips or inspirations, maybe what others have done.
I've done this in the past and found basically 2 approaches: Model-based and Controller-based.
Model-based within the model itself override the save and update methods (assuming an ActiveRecord pattern) to create a new entry in the ChangeLog table.
Controller-based add the ChangeLog record creation logic to each controller that you want to track.
I prefer the Controller-based approach since you have more control over what's going on and when. Also, you have full access to the user session so it's easier to add tracking for auditing purposes.
Have solved that much more easy as it appears with my first thoughts.
This should do it most times without a comment:
protected function log($comment = ''){
$user = $this->user();
ORM::factory('Changelog')->vaules(array(
'user_id' => $user->pk(),
'section_id' => $user->section->pk(),
'username' => $user->username.'#'.$user->section->name,
'time' => time(),
'uri' => $this->uri($this->request->param(), $this->request->query()),
'controller' => $this->request->controller(),
'action' => $this->request->action(),
'post' => serialize($this->request->post()),
'comment' => $comment,
))->save();
}
A simple $this->log() and all is done.
Result: http://charterix.sourceforge.net/log.jpg
I understand the question title seems quite open-ended but I have already done a bit of reading/experimenting around the subject and have got somewhere with Cake in addition to building my own procedural-style app. My aim is to bring over my procedural app to CakePHP.
What the App does (or wants to do)
So, I have a bunch of clients, each client has 1 or more accounts. Each account has holdings that whose value is stored for a specific date. Here's an example of the database structure
The idea is to have the standard CRUD pages for clients, accounts etc. But also to have more sophisticated pages, for example, to give a return history for a clients accounts along with portfolio statistics. (I have other tables with more info in them but for the sake of simplicity I have omitted them from this issue for the time being)
What I've done
So, having following the tutorials, I've made models for clients and accounts and managed to link (associate?) the clients and accounts tables in CakePHP. I've also made controllers and views that allow me to retrieve the clients and accounts and made index, view, edit views.
Here are my model classes:
//Client.php
class Client extends AppModel {
public $hasMany = 'Account';
}
//Account.php
class Account extends AppModel {
public $belongsTo = 'Client';
public $hasMany = 'Holding';
}
//Holding.php
class Holding extends AppModel {
public $belongsTo = 'Account';
}
What I did Before
Let's say I want to make a page that shows the portfolio return for a given client. What I did in my last (crude) app was just make a php file (portfolio.php). I would then query the database for sum of the client's holdings on each month-end using a raw SQL statement. Then I'd loop though the array of returns calculating the % change in value from one month to the next.
This data can then be presented in an html table and I could then even plot the values on a chart etc.
This is a simplistic example of course. This page/view would introduce other info like benchmark comparisons (via another database table), stats like Sharpe ratio, vol, annualized return etc.
My question
How would similar functionality to the last section be implemented in CakePHP? It doesn't seem the view/page I'm after fits into the MVC ideology. Where would the logic involved in doing calculations go (e.g. calculating returns from the array of values)?
If I could have an explanation of how this kind of thing is implemented in MVC/CakePHP it would be really helpful.
The blog tutorial makes loads of sense but I can't yet see how it would extend to more complex applications.
CakePHP as a framework lets you quite free of where you put your logic. It tells you where you should make your DB call (models) and where you should control the flow of your app (controllers) but when it comes to behind the scene logic, the docs doesn't say much.
MVC as I understand it on the other hand is quite clear
Everything that treats data should go in the model.
Everything that deals with user interaction/flow control should go in the controller.
Everything that deals with user interface should go in the view.
Applied to CakePHP
That means most of your logic remains in the Models, and very few lays in the Controllers. It's also better this way since you can easily call $this->Model->foo() from any linked controller whereas calling a function defined in a another controller is hardly encouraged due to performance issue and is really not a good practice.
Other things you can investigate are Components and Behaviors.
Components are basically a set a functions that you want to share between controllers. It has more or less the same capabilities than the Controller but does not have dedicated routes, views or model.
Behaviors are the equivalent for the models, its good when you have several models that have similar behavior and you want to keep you code DRY.
Your case
In your particular case, I would go with small functions in models. For instance, all the functions that manipulates holdings information should be in the Holding model. It's nice because you can call these functions both from the Controller or another model, you could have a preparePortfolio() function in your User model that calls a few functions in the Holding model, do all kind of arrangement and then give back consolidated data to the UserController ready to be passed to the view.
One last thing
I highly recommend you have a look at the Hash class if you deal with arrays and loops. You can avoid looping through multidimensional array by using some of this functions and it can literally boost your performance if you deal with huge arrays. It has also a much cleaner syntax than having imbricated loops.
Code sample to help
app/Controller/ClientsController.php
<?php
App::uses( 'AppController', 'Controller' );
class ClientController extends AppController {
public function portfolio( $clientId ) {
$this->Client->id = $clientId;
if( !$this->Client->exists() ) {
throw new NotFoundException( 'There are no client with this id' );
}
$data = $this->Client->portfolio( $clientId );
$this->set( compact( 'data' ) );
}
}
app/Model/Client.php
<?php
App::uses( 'AppModel', 'Model' );
class Client extends AppModel {
public $hasMany = array(
'Account' => array(
'className' => 'Account',
'foreignKey' => 'client_id'
)
);
public function portfolio( $clientId ) {
$holdings = $this->Account->Holding->find( 'all', $findParameters );
$foo = $this->Account->Holding->doThings( $holdings );
return $foo;
}
}
app/Model/Account.php
<?php
App::uses( 'AppModel', 'Model' );
class Account extends AppModel {
public $hasMany = array(
'Holding' => array(
'className' => 'Holding',
'foreignKey' => 'account_id'
)
);
public $belongsTo = array(
'Client' => array(
'className' => 'Client',
'foreignKey' => 'client_id'
)
);
}
app/Model/Holding.php
<?php
App::uses( 'AppModel', 'Model' );
class Holding extends AppModel {
public $belongsTo = array(
'Account' => array(
'className' => 'Account',
'foreignKey' => 'account_id'
)
);
public function doThings( $holdings ) {
foreach( $holdings as $key => $value ) {
// code...
}
return $bar;
}
}
Can I do this in a Controller:
$this->User->read(null, $id);
$this->User->find('list');
Is it correct?
Am I using MVC correctly?
Can these easy functions be used in a Controller? Or, do I need to create these functions in the Model? Like Model->getUser(), and have that function use Model->read().
I know that functions it's called by Model, but, when I want pass some parameters, and function makes big, for example:
$this->User->find('all', array(
'conditions' => array(
'User.active' => true,
'User.group_id' => 3,
'User.age >=' => 18
)
));
Can I call this function in Controller, or need create a custom function in Model, to call it? Like... $this->User->findSomeCustomFunction($param1, $param2, $param3)?
TLDR:
It's "ok" to call a find() from your Controller, however best practice is to put any/all find()s in your models.
If you make a habit of putting all your find()s in your models, it will make it much easier to maintain your code in the long run.
Explanation/example:
In this case, as an example, you could start with a seemingly simple function:
//User model
public function getUsers() {
return $this->find('list');
}
But later, maybe you need something more along the lines of:
//User model
public function getUsers($opts = array()) {
$defaults = array(
'findType' => 'all',
'activeOnly' => true,
);
$params = array_merge($defaults, $opts);
$qOpts = array('conditions' => array());
//active only
if(!empty($params['activeOnly'])) $conditions[$this->alias.'.active'] = 1;
return $this->find($params['findType'], $qOpts);
}
(Pardon if there are many ways to make that code better - it was just off the top of my head - It gives you the idea.)
Keeping all your find()s in the Model also keeps you from having to search through each Controller every time you want to write a find() to determine if you've used a similar find() anywhere else. If you're programming as a team, that can be a nightmare, and you're almost guaranteed to be duplicating code.
It is perfectly fine to call Model->find() from a Controller. However, you will also want follow the DRY (Don't Repeat Yourself) principles. That basically means "Don't copy-paste code everywhere."
So, if you find that you need to make this exact Model->find() call from many Controller actions, it is considered good practice to abstract it into a function call against the Model. So yes, your Controllers would then call $this->User->findSomeCustomFunction().
Nobody seems to have a problem with that so either I'm doing it wrong or no one ever tried:
I have a model "Infocenter" which has many "InfocenterArticle"s. To fetch data including the related stuff I attached the Containable behavior to both.
This worked well until now that I attached a "HasImageAttachment" behavior implemented by myself. The problem is that on contained models the callbacks of my behavior don't get called.
My Models:
class Infocenter extends AppModel {
...
$actsAs = array('HasImageAttachment', 'Containable');
$hasMany = array('InfocenterArticle');
...
}
class InfocenterArticle extends AppModel {
...
$actsAs = array('Containable');
$belongsTo = array('Infocenter');
...
}
In my Controller I call:
$conditions = array('InfocenterArticle.id' => $id);
if ($this->notLoggedIn()) $conditions['InfocenterArticle.freigabe'] = 1;
$article = $this->InfocenterArticle->find('first', array(
'contain' => array(
'Infocenter',
'Infocenter.InfocenterArticle' => array(
'fields' => array('id', 'title', 'freigabe'),
'order' => array(
'InfocenterArticle.order_index' => 'desc',
'InfocenterArticle.created' => 'desc',
'InfocenterArticle.title' => 'asc'
),
'conditions' => array(
'InfocenterArticle.infocenter_id' => 'Infocenter.id'
),
),
),
'conditions' => $conditions,
));
And I can see that my HasImageAttachmentBehavior::setup() method is called but the HasImageAttachmentBehavior::afterFind() (as well as beforeFind()) are not. Infocenter::afterFind() is called though, which enabled me to do some dirty hacking, good enough for now, but I hate it.
What am I doing wrong?
Edit: Additional info in reply to RichardAtHome's comment.
1) My behavior works on models that don't have Containable attached.
2) I made sure that afterFind() doesn't get called by putting a simple die(); in the first line. The script doesn't die().
3) Signature should be okay, I double checked.
4) I'm using CakePHP 1.3.
Thanks for your help.
Currently I don't believe CakePHP core supports Behavior across Contained models.
It may be due to possible recursion, if you have weird contain array the behaviors may be called incorrectly.
There is a long post on the CakePHP Lighthouse project regarding calling behaviors over associated models, with a few recommendations for workarounds.
http://cakephp.lighthouseapp.com/projects/42648/tickets/95-afterfind-and-beforefind-callbacks-not-working-on-associated-models-was-translate-behavior-should-translate-associated-model-data
I just wrote an extensive entry on how to deal with this type of scenario.
It is for CakePHP 2.x and PHP 5.4+.
Containable behavior alternative function
Apparently this was a deliberate point of design(??!?). So you have to upgrade all the way to 3.0 if you want associated models to behave fully like models. Sigh.
Here is an extensive discussion: https://github.com/cakephp/cakephp/issues/1730
and the most straightforward cookbook fix: https://schneimi.wordpress.com/2009/09/06/behavior-afterfind-on-model-associations/