set models based on condition in cakephp queries - php

This is probably very easy to do, but I can't seem to get my head around it right now. Let's say in a component in a cakephp application, I have a variable my_model, which contains the model of the corresponding controller that is currently using the component like:
function TestComponent extend Object
{
var $my_model; // can be either User, or Person
function test()
{
$myModelTemp = $this->my_model;
$model = $myModelTemp != 'User' ? $myModelTemp.'->User' : 'User';
$this->$model->find('all');
}
}
As you can see above in my function test() what I'm trying to do is call the correct model based on the value of my_model. So based on the condition, my query will be either:
$this->Person->User->find('all');
Or
$this->User->find('all');
When I do it like I did above, I get an error saying Fatal error: Call to a member function find() on a non-object. In order words, that error means Person->User is not an object (so, it is considered as a string).

What you're saying could be true, however, it can refer to any part of the call.
So either Person or User could be invalid, or together they causes the error. Hard to say.
Try dumping the individual objects using var_dump();
So try:
<?php
echo "<pre>";
var_dump(is_object($this->Person));
var_dump(is_object($this->User));
echo "</pre>";
?>
to determine where you're code goes wrong.
To be clear, that return value needs to be true for it to be an object.
The one that returns false is the likely culprit.
Should your question refer to the correct way to reference an object, an object is basically an array. For example:
<?php
$obj = (object) array("this", "my_function");
?>
The above example casts the array as an object. However, using multiple layers might prove to be more difficult than you'd expect.

Generally, it looks like you might be going about this all wrong. Obviously you want the models to be dynamic, but then you're hard-coding things which defeats the whole point of it being dynamic in the first place.
It also seems like you might be violating the principals of CakePHP and MVC by doing all this in a component. I'm not sure this component should really be manipulating models or assuming which models are currently in use.
However, if you want to evaluate a string as an actual object, you can wrap it in { ... } (this is valid standard PHP syntax, not Cake-specific code).
Try this:
$modelName = $this->my_model;
$model = ($modelName != 'User') ? $this->{$modelName}->User : $this->User;
$model->find('all');
Now, if this doesn't work or you get an error saying it can't find the model(s) you need to ensure the models are actually loaded and initialised in the current scope.

Related

How to check the whole chain of relational chain without check each method separately?

I often get an FatalErrorException that says Call to a member function method() on null. This happens mostly when I use (in blade) long chained sentences where one among them (models) is null. For example:
$file->owners()->first()->categories()->first()->title
so when for example here categories returns null I get this exception. I have to check each method one by one. I can't check them at a time like:
!empty($file->owners()->first()->categories()->first()->title)
!is_null($file->owners()->first()->categories()->first()->title)
isset($file->owners()->first()->categories()->first()->title)
count($file->owners()->first()->categories()->first()->title)
I still get the exception by using these, because (I guess) before getting the final parameter (here 'title') the process goes trough all methods and before it can't get to the final one the exception comes. Actually in controllers this could be guidable to make all these checks but in blade this does not come so relevant to me. Besides, this is a loop. So I am looking how I could do this check at once.
Well if you get null from the first first() call you cannot continue the method chaining. You can always use try ... catch:
try {
$file->owners()->first()->categories()->first()->title
} catch (\Exception $e) {
// do on fail
}
I'd recommend using View Presenter in your project. It really helps you to keep your code clean by moving all extra logic from your views and put it in a dedicated presenter class.
Watch this laracasts video to learn more.

How to access values of multiple parameters passed to Laravel controller

I am trying to figure out how to access two (or more) parameters passed to a Laravel controller. I know how to create the route, and the URL is created correctly, but then I can only access the first passed parameter in my controller.
Route:
Route::get('managers/{id}/{parameter2}', array('as'=>'dosomething', 'uses'=> 'ManagersController#dosomething'));
where the first parameter is obviously the $id for managers, and the second parameters is to be processed by the controller.
View:
Do Something
generates the URL:
http://domain/managers/1/2
where 1 is easily accessed as the $id for managers, but when I try to access the 2nd parameter "2" using $parameter2, e.g. using a simple return: "id=$id and parameter2=$parameter2" statement, I get an "unidentified variable: $parameter2" error.
What am I doing wrong?
Is there a better way to pass multiple parameters? I'm especially asking the "better way?" question because what I want to do is use the 2nd parameter to change a value in a database table, and using a 'get' method, somebody could change the parameter value in the URL and therefore cause mischief. Must I use a 'post' method? I'd love to be able to use a link, since that works much better with the design of my application.
Thanks!
I was asked to include the controller, which I'm happy to do. Initially, just for testing, as I mentioned, my controller was a simple return to display the values of the two passed parameters. But here is what I want to be able to do, including the actual name of the function ("update_group" rather than "dosomething") --
ManagersController:
public function update_group($id)
{
DB::table('groups')->where('id','=',$parameter2)->update(array('manager_id'=>$id));
return Redirect::route('managers.show', array('id'=>$id));
}
The update table works perfectly if I replace $parameter2 with an actual value, so that syntax is fine. The issue is that Laravel says that $parameter2 is an undefined variable, despite the fact that the URL contains the value of $parameter2 as you can see above.
And since it occurs to me that the answer to this may involve adding a function to the Manager model, here is the current
Manager.php
class Manager extends Eloquent {
protected $table = 'managers'; ... (mutator and error functions)
}
Just change
public function update_group($id)
to
public function update_group($id, $parameter2)
All looks ok in your route. Seeing the controller code would help, but likely, you may not have a second parameter in your controller's dosomething() method.
public function dosomething($id, $parameter2){
var_dump($id).'<br />';
var_dump($paremter2);
}
If that isn't the case, you can try dumping it from the route's callback to further diagnose.
Route::get('managers/{id}/{parameter2}', function($id, $parameter2)
{
var_dump($id).'<br />';
var_dump($paremter2);
});
Depending on your use case, you can pass them in a query string like so: but it isn't really the 'best way', unless you're doing something like building an API that won't use the same variables in the same order all the time.
/managers?id=1&paramter2=secondParameter
var_dump(Request::query('id')).'<br />';
var_dump(Request::query('paramter2'));

Returning Different Results in PHPUnit Mock Object

I've been working on getting our systems more compatible with PHPUnit so we can do more unit testing of our classes and have managed to get some of them working with mock objects, but I've run across a problem which I can't seem to get around.
One of the classes we have (which I'm creating a mock version of) is for queries. You pass the query string into it's "query()" method, it logs the query, runs it and returns the result. It also wraps the mysql_fetch_assoc with a method called "get_row()", which returns an array value much like the original.
The problem is that, in some methods, there's more than one query being passed to the "query()" method and as a result it needs to run through multiple while loops to load the data into different variables. I've created a simplified version below:
class object{
public function __construct($query){
$this->query = $query;
}
public function loadData(){
$data1 = queryDataSource("SELECT * FROM data1");
$data2 = queryDataSource("SELECT * FROM data2");
return Array(
"data1" => $data1,
"data2" => $data2,
);
}
private function queryDataSource($query){
$this->query->query($query)
while($row = $this->query->get_row()){
$result[] = $row;
}
return $result
}
}
class testObject extends PHPUnit_Framework_TestCase{
method testLoadData(){
$test_data = Array('name' => 'Bob', 'number' => '98210');
$query = $this->getMock('Query');
$query->expects($this->any())->method('query');
$query->expects($this->at(1))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(2))->method('get_row')->will($this->returnValue(False);
$query->expects($this->at(3))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(4))->method('get_row')->will($this->returnValue(False);
}
}
In order to escape the first while loop in $object->queryDataSource() I'm returning a boolean FALSE value, as would happen when doing mysql_fetch_assoc. The problem is that, when it tries to run the second query and fetch the data through get_row(), the mock object seems to keep returning FALSE ratehr than moving on to the at(3) point. This happens even with 4 objects, only the first will get the test data as a return value then get FALSE the second time, the others will get FALSE every time.
Does anyone know if there's a way to get around this? I tried removing the FALSE flags and just having the odd values in at(), but that had the same problem, and I tried just having it return the data for at(1-2), but that just passed all the data into the first while loop and nothing for the other.
Thanks for any help you can give, hope the description of the problem's clear enough
I can't run the code as it seems to only be pseudocode but from what I understood is that you are trying to mock like this:
Call to query, get_row, get_row, query, get_row, get_row.
The issue you seem to have run into is that the number in the ->at() matcher doesn't count up per method but per object.
So what you probably want to write is:
$query->expects($this->any())->method('query');
$query->expects($this->at(1))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(2))->method('get_row')->will($this->returnValue(False);
$query->expects($this->at(4))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(5))->method('get_row')->will($this->returnValue(False);
Or to make it a litte easer to read maybe even:
$query->expects($this->at(0))->method('query');
$query->expects($this->at(1))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(2))->method('get_row')->will($this->returnValue(False);
$query->expects($this->at(3))->method('query');
$query->expects($this->at(4))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(5))->method('get_row')->will($this->returnValue(False);
With your mocks you ran into the issue that the second call to "query" was counting up one "call" and hence skipping over the second return($test_data);.
Unfortunately the at() binds your tests to the implementations very strongly.
Imagine if you rearranged 2 method calls inside a tested method, the functionality is exactly the same but all tests using at() would now fail, often with cryptic messages such as method doesn't exist at index N
On the occasions you want to specifically say "this called exactly this way then this called exactly this way" that's great, but if you just want assertions then one of the PHPUnit Mock Extensions seems more friendly, particularly Mockery and a guide here (a touch out of date I believe)
There are others also.

getCategories method calling itself?

I'm a little confused as to what is going on here, it looks to me like a method is calling itself? I'm trying to learn about Magento's models. I was working my way back from a helper (catalog/category) and I got to a call on this method "GetCategories". I don't know whats going on here. If anyone could shed light on this code snippet I greatly appreciate it.
getCategories ( $parent,
$recursionLevel = 0,
$sorted = false,
$asCollection = false,
$toLoad = true
){
$categories = $this->getResource()
->getCategories($parent, $recursionLevel, $sorted, $asCollection, $toLoad);
return $categories;
}
Not much to add to #hakra's answer. Just a portion of Magento-specific logic.
So to work with Magento models you should know, that Magento has 2 types of Models: normal models, and resource models (we can call assign Blocks to the models too, as a view models - but that is more connected to the V part of MVC).
The resource models were created as a DB adapters that contain only DB-related logic, and often are connected to some DB table, hence contain the logic for CRUD operations with that table. So you'll see smth like this regularly - for the simplicity someMethod is a part of normal model, but since it contains DB-related logic, all the implementation of the method was moved to the resource model, so the body of someMethod in the regular model will be something like that:
public function someMethod($args)
{
return $this->getResource()->someMethod($args);
}
It is hard to say for the code you've posted. Even both methods share the same name (getCategories) it must not mean that they are of the same class or even object.
If you want to find out you would need to compare:
var_dump($this === $this->getResource());
Apart from that, it is also common in programming recursion that a method calls itself, hence recursion. However for that chunk of code, it would run against the wall.
So technically speaking I would do the assumption that in your example this is not the exact same object method.
Please take note that this answer is independent to Magento, it's just how PHP works generally.

Passing two arrays from controller in cakePHP

I'm trying my best to learn MVC and cakePHP and I had a question about passing arrays to the view. Currently, I have some basic code below.
class AwarenesscampaignsController extends AppController {
public function view($id = null) {
$this->Awarenesscampaign->id = $id;
$this->set('data', $this->Awarenesscampaign->read());
}
This is what I "think" is currently happening.
AwarenesscampaignsController is set up. The view paramater requests id and matches it up with the Model, Awarenesscampaign. This matches up with the database and returns an array which is set to the variable "$data", and then the view is loaded.
My first question: is my understanding accurate?
What I would like to do is with this is to be able to pass another array, from a different model. For instance, I would like to query the table Posts (Controller: PostsController/ Model: Post).
For instance, my first attempt was to do the following inside the function:
$this->Post->find('all');
But this yields the error:
Indirect modification of overloaded property AwarenesscampaignsController::$Post has no effect [APP/Controller/AwarenesscampaignsController.php, line 20]
Additionally, I'm not sure how I would send both variables to the view.
To recap:
Was my understanding accurate?
How do I query a variable from another controller/model?
How do I sent this array to the appropriate view for that controller?
Thanks,
-M
You're on the right lines, and aren't doing it wrong per se. I would say your understanding is pretty good for a beginner.
By default Cake automatically loads a model that it thinks is directly related to the controller. So in AwarenesscampaignController, you can automatically access Awarenesscampaign (the model).
It doesn't know about any other model, though. One way you might solve this is by adding the following property to your controller:
// This has to contain ALL models you intend to use in the controller
public $uses = array('Awarenesscampaign', 'Post');
This goes at the top of the class, before you start declaring the functions. It tells Cake that you want to use other models except the 'default' one, but you have to add that one to the array too, or you'll lose access to it.
You can also use loadModel inside your action, if it's a one-off. It's then accessed the same way as you would access a model normally:
public function view($id = null) {
$this->loadModel('Post');
$posts = $this->Post->find('all');
...
}
To send this to your view, you can call set again, but you might want to change data to something more readable, and to prevent confusion:
public function view($id = null) {
...
$this->set('campaign', $this->Awarenesscampaign->read());
$this->set('posts', $this->Post->find('all'));
}
They'll be accessible as $campaign and $post respectively.
One tweak I would make, though, is to not use 'read' unless you intend to edit something. You can use findByColumnName to get the same data. Since you're using just an id, you can call findById:
$campaign = $this->Awarenesscampaign->findById($id);
There's quite a lot of magic going on there. It just means you can search for a particular value in a more short-hand format.
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html
Finally, while you can access other models (as demonstrated), you can't, or generally shouldn't, try and access one controller from another. If you have code that you want to use in more than one controller, but can't go in the model, you can create Components.
http://book.cakephp.org/2.0/en/controllers/components.html#creating-a-component
The manual is fairly comprehensive. While sometimes hard to navigate, it will often have an answer to most of your questions.
http://book.cakephp.org/2.0/en/
1) Your understanding is good enough. What this is doing is basically mapping a row of database table with object. So after setting the Model id $this->Awarenesscampaign->id = $id, now Model is pointing to the row of database table that has id equals to what has been passed to view action.
2) you can query another table by calling the methods of that particular Model. If your model is somehow associated with the current Model that you are in, you can use chaining to call that Model's action. e.g. if your in Posts controller and Post Model is associated with Comment Model t get the data you can chain through.
$comments = $this->Post->Comment->find();
If however your Model of interest is not associated with current Model, there are couple of ways to perform operations of other Model. A good option is to use Class Registry. Say for example you want to use Customer Model which is not related to your current Model. In your controller you will do
$customer= ClassRegistry::init("Customer");
$customers= $customer->find();
3) to set multiple variables for the view you can set them via compact function or using associated row.
$posts = $this->Post->find();
$comments = $this->Post->Comment->find();
$this->set(compact('posts', 'comments'));
// or
$this->set('posts' => $posts, 'comments' => $comments);

Categories