Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fixes #1391: CDetailView: callables (including anonymous functions for PHP 5.3+) could be used as value generators of the attributes. #1405

Merged
merged 3 commits into from

7 participants

@resurtm
Collaborator

Fixes #1391.

Testing code:

<?php $this->widget('zii.widgets.CDetailView',array(
    'data'=>TaskGroup::model()->find(array('offset'=>1)),
    'attributes'=>array(
        'id',
        'title',
        'create_time',
        array(
            'name'=>'update_time',
            'value'=>function($data) {
                return $data->id.', '.$data->title;
            },
        ),
    ),
)); ?>

<br/><br/>

<?php
class TestingClass
{
    public static function testingCallback($data)
    {
        return $data->id.', '.$data->title;
    }
}
?>

<?php $this->widget('zii.widgets.CDetailView',array(
    'data'=>TaskGroup::model()->find(array('offset'=>1)),
    'attributes'=>array(
        'id',
        'title',
        'create_time',
        array(
            'name'=>'update_time',
            'value'=>array('TestingClass','testingCallback'),
        ),
    ),
)); ?>
@resurtm resurtm Fixes #1391: CDetailView: callables (including anonymous functions fo…
…r PHP 5.3+) could be used as value generators of the attributes.
c993457
@qiangxue
Owner

I don't understand why we need this. Why not just directly assign the evaluation result to 'value'?

@creocoder

@resurtm Very good enchancement!

@qiangxue Because sometimes there is some very complex logic to get result. For example:

<?php $this->widget('zii.widgets.CDetailView',array(
    'data'=>TaskGroup::model()->find(array('offset'=>1)),
    'attributes'=>array(
        'id',
        'title',
        'create_time',
        array(
            'name'=>'status',
            'value'=>function($data) {
                switch($data->type)
                {
                    case 'started':
                        return 'Started';
                    break;
                    case 'finished':
                        $time=User::model()->getLastTaskFinishTime();
                        return 'Finished at '.Yii::app()->dateTimeFormatter->formatDateTime($time);
                    default:
                        return "Unknown ($data->type)";
                    break;
                }
            },
        ),
    ),
)); ?>
@cebe cebe was assigned
@cebe
Collaborator

I also like it. in one application I created a formatter method for each special value type and placed that complex logic there. But when it is specific to one view only, annonymous function will be very helpful imo.

@qiangxue
Owner

Based on this argument, then every property of a widget should support anonymous function (even though most of them may not need it most of the time). I don't think this is a good thing.

The similar effect can be achieved without anonymous function in many different ways: reusable formatting methods, a piece of PHP statements locally, eval().

@creocoder

@qiangxue For specific view there is no need to do reusable formatting methods. Can you show example how similar effect can be achieved without anonymous function for my example?

@cebe
Collaborator

The only way I see would be something like this, but if you do that for multple columns code quikcly gets unreadable

<?php
    switch($data->type)
    {
         case 'started':
             $value1='Started';
         break;
         case 'finished':
             $time=User::model()->getLastTaskFinishTime();
             $value1='Finished at '.Yii::app()->dateTimeFormatter->formatDateTime($time);
         break;
         default:
              $value1= "Unknown ($data->type)";
    }

 $this->widget('zii.widgets.CDetailView',array(
    'data'=>TaskGroup::model()->find(array('offset'=>1)),
    'attributes'=>array(
        'id',
        'title',
        'create_time',
        array(
            'name'=>'status',
            'value'=>$value1,
        ),
    ),
)); ?>
@qiangxue
Owner

You can also use eval(), which is almost the same as anonymous function except that it uses global scope.

I care more about code consistency. Why should the 'value' property be so special here? If we change the code so that 'value' can take anonymous function, shouldn't we do the same for similar properties in other widgets?

@creocoder

shouldn't we do the same for similar properties in other widgets?

@qiangxue For what other widgets for example? From a practical point of view, the problem so far only in this widget.

@qiangxue
Owner

For example, CButtonColumn.template or viewButtonLabel: developers may want to customize them based on some complex logic. In fact, virtually any widget property has similar need, although practically this is not common for most of them.

What I'm really against is the practice that we specially handle a property because it may receive some value obtained via complex value. IMO, this adds extra burden to widget developers and is a misuse of anonymous function.

@creocoder

@qiangxue I do not think we have any place where anonymouse functions was misused.

@qiangxue
Owner

I should use the term "abuse" rather than "misuse".

@creocoder

@qiangxue I think there is two ways of solve problems:
1) reusable formatters/widgets + specific Widget for specific view
2) reusable formatters/widgets + specific Anonymous for specific view

So choose beetween Widget VS Anonymous

Widget

  • faster
  • need file to implement

Anonymous

  • no need file to implement
  • slower
@qiangxue
Owner

First, we agree the problem here: we have some specific formatting code that is used only once, and we want it to be organized well.

My opinion is that asking the widget to modify the code to support anonymous function is the wrong direction, even though it does solve the problem. This should not be the burden on the widget developer.

As an alternative solution, why not consider using eval()? is it because it is evil? :)

Also, using anonymous function, you would have trouble if you need to access variables other than $data.

@creocoder

@qiangxue

As an alternative solution, why not consider using eval()?

eval()

  • no need file to implement
  • make widget developer life easier
  • slower than anonymous

Also, using anonymous function, you would have trouble if you need to access variables other than $data.

We can use use keyword: function($data) use ($any)

is it because it is evil? :)

I think eval() is evil anyway :) Moreover i hope in Yii 2 and PHP5.3 we can totally forget this function, except places where anonymous cant be serialized,

@creocoder

But i agree that problem solve with anonymous is wrong way. I think problem is deeper than we think. For example in some other non-php frameworks which use MVC approach there is special View-Model (model of view) layer. Perhaps the problem should be solved in more detail improvements in View layer.

@qiangxue
Owner

I think problem is deeper than we think.

I agree. We should think about a better and more systematic solution.

@creocoder

Anyway i vote to add support of anonymous function to CDetailView widget, because its total unusable without it for now. Because of this, I can not use this widget for a year or more. Why ? I want consistent code in my views. I use anonymous in CGridView and i do not want half-solutions for CDetailView. Will ask the question another way:

$this->widget('zii.widgets.grid.CGridView',array(
    'columns'=>array(
        array(
           'name'=>'title',
           'value'=>function($data)
           {
               //some complex presentation logic here;
           },
        )
    )
));
 $this->widget('zii.widgets.CDetailView',array(
    'data'=>TaskGroup::model()->find(array('offset'=>1)),
    'attributes'=>array(
        'id',
        'title',
        'create_time',
        array(
            'name'=>'status',
            'value'=>..., // eval(), prepared vars, renderPartial() maybe,
                   // or maybe generate html in some fat model method o_O??? no thanks i pass.
        ),
    ),
@cebe
Collaborator

I think for yii 1.1 we can allow annonymous function here. We should merge it or close it. I'm for merge as it is a small change that does not hurt anyone and also makes some people happy :)

framework/zii/widgets/CDetailView.php
@@ -206,7 +208,7 @@ public function run()
if(!isset($attribute['type']))
$attribute['type']='text';
if(isset($attribute['value']))
- $value=$attribute['value'];
+ $value=is_string($attribute['value']) ? $attribute['value'] : call_user_func($attribute['value'],$this->data);
@klimov-paul Collaborator

Check "is_string" can be unreliable.
Better to change the condition to be "is_callable()".

@resurtm Collaborator
resurtm added a note

Sure, done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@resurtm
Collaborator

Everyone agree to merge this?

@samdark
Collaborator

OK for me.

@resurtm resurtm merged commit a26844f into from
@robertelza

Sorry for bringing it up again but in my recent project I had a little problem with this new feature. When I put a simple string in the value field (like 'virtual' or 'date' in the following code), PHP gives me this warning: "virtual() expects parameter 1 to be string, object given"

$this->widget('zii.widgets.CDetailView', array(
    'data' => $model,
    'attributes' => array(
        array(
            'name' => 'myName',            
            'value' => 'virtual'
        ),        
    ),
));

However when I replace that string with an anonymous function, the warning is gone:

$this->widget('zii.widgets.CDetailView', array(
    'data' => $model,
    'attributes' => array(
        array(
            'name' => 'myName',            
            'value' =>  function() {
                return 'virtual';
            },
        ),        
    ),
));

so my question is: Should we always use anonymous functions to avoid such situations? or maybe there is a better way?!
I'm using PHP 5.3.8

@cebe
Collaborator

I think we should change is_callable to instanceof Closure to allow only annonymous functions.

@robertelza

So I guess the answer is yes in the next release of Yii ;)

@resurtm
Collaborator

Lets continue discussion in #3010.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Sep 14, 2012
  1. @resurtm

    Fixes #1391: CDetailView: callables (including anonymous functions fo…

    resurtm authored
    …r PHP 5.3+) could be used as value generators of the attributes.
Commits on Jun 6, 2013
  1. @resurtm

    Merge branch 'master' of github.com:yiisoft/yii into 1391-cdetailview…

    resurtm authored
    …-value-closure
    
    Conflicts:
    	framework/zii/widgets/CDetailView.php
  2. @resurtm
This page is out of date. Refresh to see the latest.
Showing with 2 additions and 1 deletion.
  1. +1 −0  CHANGELOG
  2. +1 −1  framework/zii/widgets/CDetailView.php
View
1  CHANGELOG
@@ -83,6 +83,7 @@ Version 1.1.14 work in progress
- Enh #1065: CJuiSliderInput now supports ranged slider when using it without model (resurtm)
- Enh #1142: CSecurityManager::computeHMAC() has been made public (resurtm)
- Enh #1353: Added onBeforeCount event to CActiveRecord (jakob-stoeck)
+- Enh #1391: CDetailView: callables (including anonymous functions for PHP 5.3+) could be used as value generators of the attributes (resurtm)
- Enh #1447: CSqliteSchema: added enabling/disabling integrity check for sqlite (gleb-sternharz, resurtm)
- Enh #1589: Added HTTP range responses support to CHttpRequest::sendFile (Ragazzo, samdark)
- Enh #1604: Added method CDbCommandBuilder::createMultipleInsertCommand() to support multiple insertion (klimov-paul)
View
2  framework/zii/widgets/CDetailView.php
@@ -206,7 +206,7 @@ public function run()
if(!isset($attribute['type']))
$attribute['type']='text';
if(isset($attribute['value']))
- $value=$attribute['value'];
+ $value=is_callable($attribute['value']) ? call_user_func($attribute['value'],$this->data) : $attribute['value'];
elseif(isset($attribute['name']))
$value=CHtml::value($this->data,$attribute['name']);
else
Something went wrong with that request. Please try again.