2
votes

I am currently a beginner in CakePHP, and have played around with CakePHP 1.3, but recently CakePHP 2.0 has been released.

So far I like it but the only thing is being a pain is the fact that it doesn't return Objects, rather it just returns arrays. I mean, it hardly makes sense to have to do $post['Post']['id']. It is (in my opinion) much more practical to just do $post->id.

Now after Google I stumbled upon this link, however, this kept generating errors about indexes not being defined when using the Form class (guessing this is because it was getting the objectified version rather than the array version).

I am following the Blog tutorial (already have followed it under 1.3 but going over it again for 2.0)

So, anyone know how to achieve this without it interfering with the Form class?

Hosh

5

5 Answers

2
votes

You could create additional object vars. This way you wouldn't interfere with Cake's automagic but could access data using a format like $modelNameObj->id; format.

Firstly, create an AppController.php in /app/Controller if you don't already have one. Then create a beforeRender() function. This will look for data in Cake's standard naming conventions, and from it create additional object vars.

<?php
App::uses('Controller', 'Controller');

class AppController extends Controller {

  public function beforeRender() {

    parent::beforeRender();

    // camelcase plural of current model
    $plural = lcfirst(Inflector::pluralize($this->modelClass));

    // create a new object
    if (!empty($this->viewVars[$plural])) {      
      $objects = Set::map($this->viewVars[$plural]);
      $this->set($plural . 'Obj', $objects);     
    }

    // camelcase singular of current model
    $singular = lcfirst(Inflector::singularize($this->modelClass));    

    // create new object 
    if (!empty($this->viewVars[$singular])) {      
      $object = Set::map($this->viewVars[$singular]);
      $this->set($singular . 'Obj', $object);
    }
  }
}

Then in your views you can access the objects like so:

index.ctp

$productsObj;

view.ctp

$productObj->id;

All we're doing is adding 'Obj' to the variable names that Cake would already provide. Some example mappings:

Products -> $productsObj

ProductType -> $productTypesObj

I know this is not perfect but it would essentially achieve what you wanted and would be available across all of your models.

3
votes

Little known fact: Cake DOES return them as objects, or well properties of an object, anyway. The arrays are the syntactical sugar:

    // In your View:
    debug($this->viewVars);

Shwoing $this is a View object and the viewVars property corresponds with the $this->set('key', $variable) or $this->set(compact('data', 'for', 'view')) from the controller action.

The problem with squashing them into $Post->id for the sake of keystrokes is Cake is why. Cake is designed to be a heavy lifter, so its built-in ORM is ridiculously powerful, unavoidable, and intended for addressing infinity rows of infinity associated tables - auto callbacks, automatic data passing, query generation, etc. Base depth of multidimensional arrays depends on your find method, as soon as you're working with more than one $Post with multiple associated models (for example), you've introduced arrays into the mix and there's just no avoiding that.

Different find methods return arrays of different depths. From the default generated controller code, you can see that index uses $this->set('posts', $this->paginate()); - view uses $this->set('post', $this->Post->read(null, $id)); and edit doesn't use $this->set with a Post find at all - it assigns $this->data = $this->Post->read(null, $id);.

FWIW, Set::map probably throws those undefined index errors because (guessing) you happen to be trying to map an edit action, amirite? By default, edit actions only use $this->set to set associated model finds to the View. The result of $this->read is sent to $this->data instead. That's probably why Set::map is failing. Either way, you're still going to end up aiming at $Post[0]->id or $Post->id (depending on what you find method you used), which isn't much of an improvement.

Here's some generic examples of Set::map() property depth for these actions:

    // In posts/index.ctp
    $Post = Set::map($posts);
    debug($Post);
    debug($Post[0]->id);

    // In posts/edit/1
    debug($this-viewVars);
    debug($this->data);

    // In posts/view/1
    debug($this-viewVars);
    $Post = Set::map($post);
    debug($Post->id);

http://api13.cakephp.org/class/controller#method-Controllerset

http://api13.cakephp.org/class/model#method-Modelread

http://api13.cakephp.org/class/model#method-ModelsaveAll

HTH.

2
votes

While I like the idea Moz proposes there are a number of existing solutions to this problem.

The quickest one I found is https://github.com/kanshin/CakeEntity - but it looks like you might need to refactor it for 2.x - there might even already be a 2.x branch or fork but I didn't look.

0
votes

I also ran this question couple of time in my head. Now a few Cake based apps later, I see the benefit to be able to branch and merge (am, in_array etc.) result sets more conveniently with arrays than using objects. The $Post->id form would be a sweet syntactic sugar, but not a real benefit over arrays.

0
votes

You could write a function that iterates over your public propertys (see ReflectionClass::getProperties) and save it in an array (and return the array).

If you have access to the class, you can implement the ArrayAccess Interface and easily access your object as an array.

P.S.: Sorry, i've never used CakePHP but i think object-to-array conversion doesn't have to be a framework specific problem