How to read CakePHP Model using specific fields? - php

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>.

Related

How to pass current user ID to buildRules method of CakePHP model

I use AuthComponent with CakePHP 3.8 and now I need to do some logic in Model buildRules method but for this I need to get the current user ID.
Is there any way to pass/retrieve it without using hacks such as accessing directly from the session.
I know that it is possible to pass id via validator from controller as described in CakePHP's documentation
https://book.cakephp.org/3/en/core-libraries/validation.html#using-custom-validation-rules
And it works for validation, however, I am unable to access the validator from the inside of build rules.
When I do as described in here, I get an empty object.
https://book.cakephp.org/3/en/orm/validation.html#using-validation-as-application-rules
It seems that I am able to attach new validation rules but unable to retrieve the "Passed" provider to get the User ID.
It seems a trivial thing but a I spent quite a few hours trying to get the id in a proper way.
OK, After working a bit more, I found how to retrieve user_id inside build rules. Might be helpful to someone.
Do this in the controller
$this->ExampleModel->validator('default')->provider('passed', [
'current_user' => $this->Auth->user('id')
]);
And then put this in you buildRules method
public function buildRules(RulesChecker $rules)
{
$user_id = $this->validator('default')->provider('passed')['current_user'];
$rules->add(
function ($entity, $options) use($user_id) {
//return boolean for pass or fail
},
'ruleName',
[
'errorField' => 'some_id',
'message' => 'Some ID field is inconsistent with App logic.'
]
);
return $rules;
}
The proper way to handle this is usually using either events, or saving options.
For example to make the ID available for the application rules of all tables, you could do something like this:
$this->getEventManager()->on('Model.beforeRules', function (
\Cake\Event\Event $event,
\Cake\Datasource\EntityInterface $entity,
\ArrayObject $options
) {
$options['current_user'] = $this->Auth->user('id');
});
It would be available in the $options argument of the rule accordingly, ie as $options['current_user'].
For a specific save operation you can pass it to the options of the save() call:
$this->ExampleModel->save($entity, [
'current_user' => $this->Auth->user('id')
]);
There's also plugins that can help you with it, for example muffin/footprint.

One get method for each table field (model)

I have two models that have a relationship to each other. In one particular method I need to pick a field on the other, I usually create a method to pull just one field, if the field in the future subject to change, I will have to change only the return of function .. here are examples
I currently use the following: (this is just an example, obviously there is much more fields to get)
User model
function getUsername($user_id){
$this->id = $user_id;
return $this->field('my_username_field');
}
Server model
function getUserIdByServerId($server_id){
$this->id = $server_id;
return $this->field('my_user_id_field');
}
function getUsernameByServerId($server_id){
$user_id = $this->getUserIdByServerId($server_id);
return $this->User->getUsername($user_id);
}
This is a lot of code to write, because if I want to get more fields, I would have to write kind of one method for each field.. and if I do otherwise then when the field name change I'll have to re-write his name on all calls.. what is the better way?
Why do you want to fetch single fields? Especially if you need more than one? This doesnt make much sense. It is more effective to fetch the whole record (all fields, or select the 3-4 you need) and then deal with the data instead of doing multiple queries to the DB. That is inefficient and repetitive, not very DRY.
CakePHP already features a method for that, Model::field().
$this->Server->User->field('username', array(
'conditions' => array(
'User.server_id' => $serverId
)
));
But like I said, why are you not just doing this?
$this->Server->find('first', array(
'contain' => array(
'User'
),
'conditions' => array(
'User.server_id' => $serverId
)
));
You shouldn't need to create new methods in the models for this. What you would be doing now, I guess, in your Server Controller, would be something like this:
$this->Server->id = $server_id;
$username = $this->Server->getUsernameByServerId($this->Server->id);
What I think you can do, though, is just call something like this:
$this->Server->id = $server_id;
$username = $this->Server->User->field('username');
Or if you have (I think) PHP 5.4 or higher just use:
$this->Server->id = $server_id;
$username = $this->Server->read()['User']['username'];
(with PHP <5.4 you can just split that last line into two).
I don't think you should have to copy and paste loads of methods in the models for this.

How to make a Changelog?

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

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

Passing variables to views in Laravel4

I am very newbie about all of these PHP Frameworks. I was once created my own framework by using phpbb's template functions, language support and sessions. I turned them into a Model-View framework. I decided that is too complicated and searched for a new framework.
Right now I am using laravel and its quite well yet I still couldn't figure how to handle controllers and views. Here comes my stuck part.
I was using my phpbb's framework by creating a file.php to root folder and create a .html file styles folder. phpbb's framework can render a html file by calling
$template->set_filenames(array(
'body' => 'file.html'
));
however i can pass every variable to file.html from the controller.php like this :
$template->assign_var('THREAD_ID', $row['id']);
$template->assign_var('THREAD_NAME', $row['title']);
even cycles were too easy
while ($row = $db->sql_fetchrow($result))
{
$template->assign_block_vars('post_row', array
(
'ID' => $row['post_id'],
'COUNT' => $count++,
'USERNAME' => $row['post_username'],
'DATE' => $row['post_datetime'],
'ENTRY' => $row['post_entry'],
)
);
}
and then for rendering the view
$template->set_filenames(array(
'body' => 'file.html'
));
this is what i couldnt understand in laravel.
I am using this but when I using this for another variable it gives me error.
$this->layout->nest('content', 'index', array(
'data' => 'pokeçu'
));
in documents they made examples just for one variable. I dont know how to continue my way.
I'm assuming you're using Laravel 3, as that is the current stable version. There's a section in the Laravel docs covering this. Basically you're going to be using the View class in whatever way suits your app best. Remember, your controller methods (or route closures) will always return something, usually a View instance. To bind data to that view, the simplest method is to use with($data[, $value]) where $data is either an associative key-value array, or $data is a key and $value is the value. For example:
public function get_index()
{
$thread = array('id'=>23, 'name'=>'Skidoo');
return View::make('home.index')->with($thread);
}
Note the return. You don't need to return it right away. You can also instantiate the View object, and bind data to it directly:
public function get_index()
{
$view = View::make('home.index');
$view->thread = array('id'=>23, 'name'=>'Skidoo');
$view->welcome = 'Welcome to My Site!';
return $view;
}
In addition to the docs, there are a couple of recent tutorial books available. Check out the Learn section on the Laravel website at http://laravel.com/

Categories