One get method for each table field (model) - php

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.

Related

Using two tables, where does the data retrieval go (First step in MVC)

I am new to MVC proramming (And even object based). So coming from PhP 4.* I moved into OOP, MVC and Cake..
I am building a site which Insitutes from difference COUNTRIES can use to store their data (And more). I am now building the basic per-institute registration, and would like to include a drop-down of countries.
I see two ways to approach this; Either retreive the country table information for a dropdown using the Country model:
$this->set('countries', ClassRegistry::init('Country')->getAllCountries()); (Followed by a function in \Model\Country.php)
or use the InstitutesController:
$this->set('countries', $this->Institute->Country->find('list', $params = array('fields' => array('id', 'country'))));
Which is the recommended route to take, as both seem to work?
Use the second one:
$this->set('countries', $this->Institute->Country->find('list', $params = array('fields' => array('id', 'country'))));
Both work, but, you've already got an instance of your country model (accessible via $this->Institute->Country). So, why create another instance of it? There's just no need.
There shouldn't actually be a need to specify the fields for your call to the find method. 'id' will be automatically selected as the first field, and if you set the display field of your Country model to 'country', then that will be the default uses in find('list') calls. Do it like this:
// just after class Country extends AppModel {
public $displayField = 'country';
Then, you'll just need to use this code:
$this->set('countries', $this->Institute->Country->find('list'));

How can I use Model functions correctly according to the MVC pattern in CakePHP?

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

Re-sorting a CActiveDataProvider

I am looking for a way in Yii to re-sort a CActiveDataProvider.
Currently I am using the the data provider to fetch sorted and paginated data.
I am then looping through it with a foreach loop adjusting data for specific fields, and now I need to re-sort it based upon the new adjusted data.
I can't sort the data in the model using afterFind because I need to query another DB (MySQL) to work out a calculated value and it doesn't seem to like the switching during the processing.
I don't want to use an CArrayDataProvider because there is no obvious way to paginate the data which comes back unless I put controls into a loop while adjusting the data, however I don't know how much data could come back, say 200 records, but the only adjusting 20 for the display seems counterintuitive.
This all is then pushed to a CGridView widget.
so now I need to readjust on the array below in ascending order for example.
$dataProvider = new CActiveDataProvider( blah );
foreach ( $dataProvider->getData() as $data ) {
$data->Score += SomeModel::model()->findByPk(1)->NewScore;
}
array(
'Score' => 7
),
array(
'Score' => 6
)
$this->render('blah', array('data' => $dataProvider);
It sounds like you should simply implement a new CDataProvider of your own that wraps an existing CDataProvider. You will need to override several methods, but for most of them you can simply forward to the wrapped data provider. The code might end up looking something like this:
$dataProvider = new CActiveDataProvider(...);
$myProvider = new CustomDataProvider($dataProvider);
// you could make CustomDataProvider work like this:
$myProvider->dataMutator = function(&$row, $key) {
$row->Score = SomeModel::model()->findByPk(1)->NewScore;
};
$this->render('blah', array('data' => $myProvider);
CustomDataProvider::fetchData might look like this:
protected function fetchData()
{
$data = $this->wrappedProvider->fetchData();
array_walk(&$data, $this->dataMutator);
uasort($data, function($a, $b) { return $a->Score - $b->Score; });
return $data;
}
I 've hardwired the sort here -- this works for prototyping, but to specify the post-processing sort cleanly is not trivial because:
CDataProvider exposes a sort property which is ignored. Integrating that property fully would either be non-trivial work (you have to write code that respects multiple sort criteria etc) or change the semantics of CustomDataProvider.sort compared to sort on its base class. CDataProvider is married to CSort, so if you wanted to go for the cleanest solution from an OO perspective it would be best to implement IDataProvider from scratch.
It's not intuitive to the user of CustomDataProvider how sorting works if a single 'sortproperty is used, becausesort` cannot reflect both what the wrapped data provider will do and what the "post-processing" sort will do at the same time; however, both of the above will affect the end result.
I suggest that you get a prototype working and then have a good think about what the object model should be.

MVC Fundamentals: Passing work along to the view?

I'm using Doctrine2 and CodeIgniter2, and am new to both, as well as to OOP/MVC, so please use simple explanations :)
For testing purposes, I have a Controller a Model and a View. I want to display data from a table that contains user information. First name, last name, ID number, and so forth.
My controller makes a call to the model- which retrieves data from the doctrine entity, and then the controller passes that data to the view.
(controller)
class Test_v_to_m extends CI_Controller {
public function index() {
$this->load->model('testing/test_v_to_m_model');
$data = $this->test_v_to_m_model->display_user_info();
$this->load->view('testing/test_v_to_m_view', $data );
}
}
(model)
class Test_v_to_m_model extends CI_Model{
public function display_user_name() {
$query = $this->doctrine->em->createQuery("select u from ORM\Dynasties2\Users u");
return $query->getResult();
(view)
//print_r($data);
First question is: How do I pass the object or array along to the view in a useful way? This works if I'm just dealing with a single variable:
(controller)
$user = $this->doctrine->em->find('Entities\User', $user_id);
$data['firstname'] = $user->getFirstName();
$this->load->view('testing/test_v_to_c_view_2',$data);
(view)
echo $firstname;
But I don't know how to do something similar when its an array, or a multidimensional array.
The second question is whether or not to let the view do any real work (php logic, loops, foreach, etc) or to do all of that in the controller and have the view only do formatting and display.
Yes, You can just pass multi-dimensional array to the view and then access it as required.
e.g.
$template_date['result_arr'] = array(
array('firstname' => 'abc', 'lastname' => 'xyz')
, array('firstname' => 'abc', 'lastname' => 'xyz')
);
in your view file -
foreach($result_arr as $key => $row) {
echo $row['firstname'].' <br />';
}
Re your 2nd question - As per my understanding - it's fine to use some foreach, for loops in the view but it's best if business logic is kept to controllers and models. Hope it makes sense to you.
As for your first question, I don't know the answer off the top of my head (sorry!). I would imagine, however, that an array can be passed as part of the data (as a single item), but you would need to iterate though it in the view (see below). Just a guess, however...
As for your second question, the principle of MVC is to have only display logic in the view - so all of the "real work" should be done in the controller.
Now, if you want to have a loop to display data in a table, that's "real work" being done in the view, but since it's part of formatting and display that would be acceptable.
Regarding your first question, it's actually quite simple:
$data = array(
'firstname' => 'string',
'array' => array(1, 2, 3),
'multidimensional_array' => array('ocean' => 'deep')
);
In the view, you can access these as:
$firstname;
$array;
$multidimensional_array;
They're just exported to the view, so you can treat each key in the $data array as a variable, and the values in the $data array as the variables' values.
Regarding the second question, it is generally best if you have the view only do formatting and display. In some cases, it might be useful to use ifs or loops, for example, if you want to display different messages based on a certain variable, or if you want to fill a table with a bunch of rows. However, I strongly recommend that you keep out as much logic as possible. The view is meant to receive all the data it needs and display it in a way that suits it.
There are plenty of reasons for this, namely maintainability (if your logic changes, you don't need to update the view), reusability (if you make views as general as possible, you can reuse them very easily) and even the ability to create new views or to replace that view with a different one, without worrying about the logic.
I hope this helps. :)

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