A couple of ideas for improving the component concept in Yii

A couple of thoughts keep popping back into my head. I have touched on a couple of these subjects before, but all these little stray thoughts are finally starting to crystallize.

First, why is the initialization feature exclusive to CApplicationComponent, and not generally available for any CComponent?

Lots of components actually need initialization after they’ve been constructed and configured - this feature is not just useful for application components. I find my self needing it again and again, and having to manually implement the IApplicationComponent interface.

The main difference between a CApplicationComponent and a CComponent, of course, is the fact that a CApplicationComponent is a pre-configured, lazy-loading object, which is a (virtual) member of CApplication.

In contrast, a CComponent is usually created and configured as needed, typically using Yii::createComponent() - which does not support initialization, and so, if your component does need initialization, you have to implement and call an initialization method yourself.

Of course, you could argue that the initialization feature wouldn’t be useful for CComponent, because there is no preset configuration that you can apply to it - and hence, nothing to initialize.

Which brings me to my next issue - why isn’t there a general mechanism that allows you to pre-configure components?

As it turns out, there is - but only for another specific type of component, namely CWidget - when used with a CWidgetFactory, you get the "skin" feature, which is essentially just a simple way to preconfigure components.

Why is that feature exclusive to widgets, and not generally available for components?

Here’s what I would recommend:

  • Drop the “skin” feature - it’s a terribly misleading misnomer in the first place.

  • Refactory CWidgetFactory into a more general-purpose component, e.g. CComponentFactory - adding support for preset configurations for any component, not just widgets.

  • Add a second (optional) argument to Yii::createComponent() that specifies which configuration to apply - let it default to "default", so that in your configuration file, you can specify a standard configuration for any component, and additional alternate configurations as needed.

  • Make the inititialization feature generally available in Yii::createComponent(), rather than specifically just for CApplicationComponent.

For backwards compatibility, CWidgetFactory could probably extend CComponentFactory, so that existing applications that don’t require CComponentFactory would need minimal changes.

My philosophy when it comes to frameworks, is that every framework feature should be as available as possible - and it seems that a couple of these features have been placed too deeply in the class hierarchy. Configuration and initialization are general-purpose features, not just useful to application components or widgets, but everywhere.

Widgets are still distinct from generic components: they solve UI-specific tasks, such as rendering views and auto-generating IDs - they belong to a controller, and they (optionally) execute in two stages, in a view-context. But the ability to pre-configure these components isn’t related to solving UI-specific task, and is not an exclusive requirement for this type of component.

Application components are still distinct from generic components: they are members of the application object, and they can lazy-load. But the ability to initialize after configuration isn’t an exclusive requirement for this type of component.

That’s why I think these features need to be untangled from application components and widgets, and made more generally available.

As a side note, whether you’re programming a framework or an application, when you encounter a new requirement, always ask this question:

Is that feature an exclusive requirement for the class where I encountered this need, or can I delegate this responsibility higher up the class hierarchy?

Just because you encountered the need here first, doesn’t mean it’s the only place where the solution could become useful.

Of course, even if you can delegate a responsibility up higher, you also need to ask whether it makes any sense to do so :wink:

In this case, as explained, I think it makes a lot of sense to make some of these features more generally available to framework components…

I have same thinking. :D

The key questions are:

  1. How to keep backward compatibility (we probably will implement this in 1.2 so that we can break BC to reach perfectness).

  2. How to do this efficiently (should this be applied to ALL components? I doubt so. For example, you probably don’t want this with AR objects because of efficiency concerns. Need further discussion on this)

You are totally right about application components. We should limit the number of application components because reading their configuration will add to the bootstrap overhead.

First of all to mindplay those are great ideas and it is good to see them written down as they helped me -being new to this framework- think a bit. My thanks.

To qiang with regards to backward compatibility, my thoughts are to consider a phased approach which would require of course a solid plan, a renaming scheme that would minimize upgrade changes or even allow for simply changing the classes being extended -possibly using mid level class with interfaces- for other developers and their work in process and also allow them to make the upgrade when they deem it convenient to do so. In addition, a posted time frame when the original work could be depreciated would help. Yes, it is simplified as these are just thoughts on the matter.

I should also add that based on the approach of lazy versus eager -pre-configured versus self-configured- objects this should be a choice for the individual developer. Developers new to this framework and even some advanced developers may wish to choose the lazy approach. Pre-configured can be handy in times of prototyping…

One other thing to consider about backward compatibility is that this frame work is still young enough to withstand a branch release or two before getting to a solid state. Production sites would continue to run on their current version, while others who wish to be more daring could take on a branch release and report back on the level of transparency achieved in the move forward. It would also be great to see modularity in branch releases by category so that distributed branch releases could remain smaller.

Thanks for listening,

Fizz

I agree with Fizz’ comments - and yes, I would wait wait for 1.2 with a major change like this.

I don’t personally think backwards compatibility is more important than the evolution of a framework - if 1.2 can go further, backwards compatibility should not be what holds back the progress. After all, we’re going to build new applications in the new version of the framework, and we all would like to grow and learn with the framework, I think? :slight_smile:

Well, only Yii::createComponent() can actually apply the configuration and initialize the component - so it’s not a requirement that components be created by Yii::createComponent(), they’re just components, you can create and initialize them any time you want - for components that do require initialization, you have to manually initialize them as it is, and you can still choose to do that.

As you pointed out, you will probably want to do that with CActiveRecord, for performance reasons.

Just for the record, I came up with a very simple way to pre-configure components - just put the configuration in the $params property of the application - for example:




<?php


return array(

  ...

  'params' => array(

    'MainMenu' => array(

      'class' => 'Menu',

      'id' => 'main-menu',

      'items' => array(...),

    ),

  ),

);



Since Yii::createComponent expects an single array as the argument, you can simply package your component configurations like this, and to create them:




<?php


$menu = Yii::createComponent(Yii::app()->params['MainMenu']);




I still think it would be nice if component lifecycle could be managed in a more generic way, as discussed, but this is actually a nice, simple and clean approach. It’s also explicit - it’s obvious from looking at the code where the configuration can be found. And finally, it’s flexible - you have a chance to modify or add to the configuration before the component is constructed.

Now that I realize how simple and elegant this is, I’m not sure I want to use the CWidgetFactory anymore - I’m realizing that it doesn’t really add anything you can’t already do. For that matter, I probably wouldn’t use a CComponentFactory.

If you have too many skins and want them in external files, you could do this:




<?php


$widget = Yii::createComponent(require(Yii::getPathOfAlias('widgets.mainmenu')));



Plain and simple, with no overhead from adding a CWidgetFactory.

Any thoughts to challenge my new point of view? :slight_smile:

I would rather see backwards compatibility break after a major release… 2.0… No fund updating the framework if the application is going to break :) (or will older versions still be maintained?)

Another point is the CWidget and Cportlet are confusing… I think they more or less do the same thing.

Also can we have some default structure in components… right now a webapp only has components… but to keep it organized most people seem to make

components/portlets

components/widgets

components/helpers

components/behaviors

And then some front controller (does that really belong in components)?

And then where do components go?

components/components? :)

Speaking of major changes, please think about "widgetizing" CController (making it embeddable into a layout of another controller) or endowing CWidget with its own actions. Either way should eventually lead to making it possible to encapsulate large chunks of functionality like a forum into widgets (or whatever other name you call them). Right now CWidget is a bit of a crippled orphan or a clumsy appendage to the framework. See also this discussion.

And it would be nice to configure controllers and specify actions via configuration

& Widget skinning is ugly, agree with it

& Default structure in components is very good idea