Tuesday, April 5, 2011

Re: Objectized model data (again)

On Tue, Apr 5, 2011 at 6:01 PM, Chris <cs@clicksports.de> wrote:
> Hi all,
>
> first excuse my bad english, I am not a native speaker. Also, the code
> examples below could be formatted wrong. I have done a couple of Cake-
> based applications in the last few years, but always had a problem
> with the arrays Model::find() and other Model-Methods return me. In
> the last projects, I tried different ways to "fix" this issue (which I
> know is not a bug in Cake, but is very verbose in views when looking
> at the examples provided):
>
> Config for the testcases (Note this is not code from a actual product,
> but just for a better understanding of what i try to archive):
>
> Model: Product (has 6 fields for testing: id, name, description,
> image, created, modified)
> Controller: ProductsController:
>
> .....
> public function show($id) {
>
>  $product = $this->Product->find('first', array(
>    'conditions' => array('Product.id' => (int) $id)
>  ));
>  $this->set(compact('product'));
> }
>
> public function showall() {
>
>  $products = $this->Product->find('all');
>  $this->set(compact('products'));
> }
> .....
>
> The view (without any error or whatever management):
>
> ------------------------------------------------------------------------------------------------------------------------
> 1. Project: "Regular" CakePHP Syntax
>
> <?php // Action: show() ?>
> <h1><?php echo $product['Product']['name'] ?></h1>
> <div class="description">
>  <?php echo $product['Product']['description'] ?>
> </div>
> ------------------------------------------------------------------------------------------------------------------------
>
> So far so good, there is nothing special in here. But if I need to
> call this in a loop....
>
> <?php // Action: showall() ?>
> <h1>All products</h1>
> <?php foreach($products as $product): ?>
>  <div>
>    <h2><?php echo $product['Product']['name']</h2>
>    <p><?php echo $product['Product']['description'] ?></p>
>  </div>
> <?php endforeach; ?>
>
> The syntax is understandable, but it is still far too verbose for me
> (e.g. calling the array index 'Product' again even if I know I just
> have the product). So, I know I could use something like Set::Combine
> to flatten the array, but the problem would still be there: I always
> have this ugly array syntax. So my second approach was defining a
> helper function:
>
> ------------------------------------------------------------------------------------------------------------------------
> 2. Project: Using a Helper for getters and clearer syntax
>
> /app/views/helpers/product.php (Simplified)
> ......
> public function getItem($item) {
>  $this->item = $item['Product'];
> }
>
> public function getName() {
>  return $this->item['name'];
> }
> ......
>
> <?php // Action: show() ?>
> <?php $this->Product->setItem($product['Product'] ?>
> <h1><?php echo $this->Product->getName() ?></h1>
> <div class="description">
>  <?php echo $this->Product->getDescription() ?>
> </div>
>
> <?php // Action: showall() ?>
> <h1>All products</h1>
> <?php foreach($products as $product): ?>
>  <?php $this->Product->setItem($product) ?>
>  <div>
>    <h2><?php echo $this->Product->getName() ?></h2>
>    <p><?php echo $this->Product->getDescription() ?></p>
>  </div>
> <?php endforeach; ?>
> ------------------------------------------------------------------------------------------------------------------------
>
> This approach is (imho) much more readable (and, this is the plus for
> me, is much easier to use for our frontend developers), but adds
> complexity to the application. The next problem is that the given
> helper only holds the reference to the last called product, so you
> cant do something like:
>
> ------------------------------------------------------------------------------------------------------------------------
> <?php // Action: showall() ?>
> <h1>All products</h1>
> <?php
> // Main product is a single product we want to call here and later as
> example
> $this->Product->setItem($mainproduct);
> echo $this->Product->getName(); // -> Returns Main Product
>
> <?php foreach($products as $product): ?>
>  <?php $this->Product->setItem($product) ?>
>  <div>
>    <h2><?php echo $this->Product->getName() ?></h2>
>    <p><?php echo $this->Product->getDescription() ?></p>
>  </div>
> <?php endforeach; ?>
> <?php echo $this->Product->getName(); // -> Returns last product in
> foreach, but I want my main product :(
> ------------------------------------------------------------------------------------------------------------------------
>
> The next drawback is that you have to define all the getters manually
> and need one helper for each model you want to parse this way. But
> with all the drawbacks, it still is a better approach than the regular
> array syntax. As I am not very happy with it (because of the
> complexity it adds).
>
> The question is: Would it make sense (from a MVC pov as much as from a
> performance and useability-standpoint for my poor frontend-guys) to
> make this even easier? What I would like to do is something like:
>
> ------------------------------------------------------------------------------------------------------------------------
> // 1. in app_model::afterFind():
> ... get the model schema and automagically create getters for our
> "ViewObject" (dont know how to call this, as I dont want to use the
> Model-Object directly but a own object with only some getter
> functions, so the users in the views cant call Model-Actions
> directly):
>
> the Product Model has, as described above some fields, so we would end
> with an object like this:
> class ProductViewModelWhatever {
>  public function getId() return $this->id;
>  public function getName() return $this->name;
> }
>
> This could be used in the view directly like:
> ------------------------------------------------------------------------------------------------------------------------
> <?php // Action: showall() ?>
> <h1>All products</h1>
> <?php
> // No longer needed
> /*$this->Product->setItem($mainproduct);
> echo $this->Product->getName(); // -> Returns Main Product*/
> echo $mainproduct->getName() // -> Returns mainproducts name
>
> <?php foreach($products as $product): ?>
>  <div>
>    <h2><?php echo $product->getName() ?></h2>
>    <p><?php echo $product->getDescription() ?></p>
>  </div>
> <?php endforeach; ?>
> <?php echo $mainproduct->getName(); // -> Returns our mainproducts
> name. Yippie! :)
> ------------------------------------------------------------------------------------------------------------------------
>
> The question is: What would be the drawbacks of such a technique? What
> do you think of the idea generally? If you have any other tips or
> ideas for accomplishing the same (or a similiar task) I would be happy
> if you would share them with me.
>
> Greetings from Germany,
> Chris

It seems to me like a lot of extra work for little benefit. And you
need to have access to the model in the view, which goes against the
MVC paradigm.

I was a bit put off by the array structures of Cake at first, also,
but have now completely embraced it. I think that one big step towards
removing the appearance of verbosity is to not follow the manual's
suggestion for variable naming. I agree that something like
$product['Product']['name'] looks really stupid and is even a bit
tiring just to read when you've a got an entire template of this sort
of thing. Instead:

$this->set('data', $this->Product->read(null, $id));
view:
echo $data['Category']['name'];
echo $data['Product']['name'];

$this->set('data', $this->paginate());
view:

foreach($data as $d)
{
echo $d['Product']['sku'];
}

Doesn't that seem more sane? As long as you're aware of the structure
of the initial array this is a breeze. The big thing one needs to be
careful of is paying attention to the difference, in the view, between
$data and $this->data. The latter belongs to the View object, and is
passed from the controller's $this->data. And, of course, for an
action with a form, one would still do:

$this->data = $this->Product->read(null, $id);

You're not using set() to create a $viewVar here. As noted above,
$this->data is passed to View directly.

Of course, you're free to use some other name to avoid any mixup.

The other thing about Cake's array structure that bugs some people is
the way that nesting occurs. It can be a bit confusing when sometimes
you have:

Array(
Image => Array(
...
Thumbnail => Array(...)
)
)

And other times:

Array(
Image => Array(...),
Thumbnail => Array(...)
)

It all depends on how you do your find(). Did you use read()? Or Containable?

The other issue with the structure new users may have is the
difference between finding one record or many. But a few minutes with
debug() should sort that out.

Well, there is one other issue, and that's how $results is passed to
callbacks like afterFind(). That's still a bit mysterious to me,
actually.

--
Our newest site for the community: CakePHP Video Tutorials http://tv.cakephp.org
Check out the new CakePHP Questions site http://ask.cakephp.org and help others with their CakePHP related questions.


To unsubscribe from this group, send email to
cake-php+unsubscribe@googlegroups.com For more options, visit this group at http://groups.google.com/group/cake-php

No comments: