CakePHP Containable doesn't fire other behaviors' callbacks on contained models? - php

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/

Related

Can't XClass eventnews Location and Organizer

I like to extend (own extension) eventnews (https://github.com/georgringer/eventnews) to use tt_address (https://github.com/TYPO3-extensions/tt_address) records for locations and organizers. The TCA is no problem, but XClass'ing does not work:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects']['GeorgRinger\\Eventnews\\Domain\\Model\\Location'] = array(
'className' => 'TYPO3\\TtAddress\\Domain\\Model\\Address',
);
$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects']['GeorgRinger\\Eventnews\\Domain\\Model\\Organizer'] = array(
'className' => 'TYPO3\\TtAddress\\Domain\\Model\\Address',
);
Has it something to do with the PHPDoc annotations in Classes/Domain/Model/News.php ?
XClass'ing the News Model will help maybe, but i want to understand why it's not possible to use the code above
This is most likely because of the proxy class generator in the news extension. It changes the way classes are loaded in order to allow overriding and extending the news model.
https://docs.typo3.org/typo3cms/extensions/news/DeveloperManual/ExtendNews/ProxyClassGenerator/Index.html

Cakephp need session var in model

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 */
]
]
]

How to use CakePHP for client portfolio management application

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;
}
}

Rest Interface for MySQL DB with ACL

99% of what REST API's do is serve as a controlled interface between client and DB, and yet I can't for the life of me find any libraries that do just that.
All libraries focus on providing a REST interface to the developer, who then sets up the communication with the database. It seems like a no-brainer to me to create a library that already interfaces with the database, and all the developer needs to do is define some ACL rules and plug in some logic here or there.
So, before I continue and put my thoughts into actions by actually creating this sort of library, may I just ask anyone with knowledge on the subject; has anyone implemented anything like this yet? Will I be re-inventing the wheel?
I'm talking strictly about a PHP based solutions by the way, I have nothing against other languages, PHP is simply my cup of tea. But for that matter, I haven't found any implementations in other languages either.
And in case my explanation doesn't make it very clear, this is essentially what I'd want:
<?php
class post_controller extends controller {
protected static $config = array(
'select' => true,
'insert' => true,
'update' => true,
'delete' => false,
'fields' => array(
'id' => array(
'select' => true,
'update' => false
),
'name' => array(
'select' => true,
'update' => true
),
'content' => array(
'select' => true,
'update' => true
)
)
);
/**
* GET, POST, DELETE are implemented already by the parent controller
* Just overriding PUT to modify the content entry
*/
function put($data) {
$data->content = htmlentities($data);
return parent::put($data);
}
}
?>
Thanks in advance for anyone giving their input and apologies if this is not a proper Stackoverflow question.
Edit:
To clarify, this type of service would be for specific use-cases, I don't imagine it to be a type of thing that anyone can use for any type of web service.
I have built a similar system for SOAP and must say it's very easy to do so. I haven't seen any prebuilt libraries that would help you do it (and I doubt they exist - see next paragraph), but it shouldn't take you more than a few hours to build your own solution (a day max - with testing and documentation writing included).
It is however a whole another question if you really want to do that. REST can be (mis)used for this purpose, but it is meant for manipulating resources. Records in a database only rarely have a one-to-one mapping with resources. If they do in your case (as they did in mine) then feel free to do it, otherwise it would be nicer to provide a proper REST API. Why expose your internal DB structure to the world? YMMV, of course.

How to read CakePHP Model using specific fields?

I'm new to CakePHP and I'm stuck in reading a Model using other fields. I did a cake bake command to generate a simple users CRUD. I can view the user using the url CakePHP provided.
/users/view/1
I can view the user using id = 1. What if I want to view a user by name instead of id?
/users/view/username
By default the view function reads the User model by id.
$this->User->read(null, $id)
Thank you.
you can use find function or findBy<Field>() in your case findByUsername()
check this
I've never used cakePHP myself but I'm going to suggest that you will likely have to implement a new user model method, something like getUserByUsername($username)
This would then in turn interface with your DAL that would get the details of that user based on the username and return a user object that can be used however you wish...
It seems that CakePHP is focusing to deprecate some functions, such as findAll(). Perhaps soon the magic methods such as findBy<field>() will have the same fate.
I can recommend what martswite is suggesting, you should create your custom function:
function findUser($username=''){
return $this->find('first', array(
'conditions' => array(
'User.username' => $username
)
));
}
Perhaps you have a status field, maybe the profile isn't public, you can add a condition:
function findUser($username=''){
return $this->find('first', array(
'conditions' => array(
'User.username' => $username,
'User.status' => 1
)
));
}
I think that's more modular than findBy<Field>.

Categories