Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Extract Object and Component into traits #12929

Closed
wants to merge 8 commits into from

Conversation

rob006
Copy link
Contributor

@rob006 rob006 commented Nov 6, 2016

Q A
Is bugfix? no
New feature? yes
Breaks BC? no
Tests pass? yes
Fixed issues #9748

This is only proof of concept, it still need adjust naming and fix descriptions.

Closes #9748

@samdark samdark added this to the 2.1.0 milestone Nov 6, 2016
@samdark
Copy link
Member

samdark commented Nov 6, 2016

I don't think Object::className() is useful since we're raising PHP requirements and there's going to be native ::class support.

@samdark
Copy link
Member

samdark commented Nov 6, 2016

Makes sense to me overall. Do you think it worth also separating events and behaviors?

@rob006
Copy link
Contributor Author

rob006 commented Nov 6, 2016

This could be merged into 2.0.x IMO. And if we want to prepare to drop Object and Component and propose an alternative to deprecated Object and Component it should provide Object::className() implementation too.

Do you think it worth also separating events and behaviors?

Yes, but I'm not sure if there is a clean way to do this.

@samdark samdark modified the milestones: 2.0.x, 2.1.0 Nov 6, 2016
@samdark
Copy link
Member

samdark commented Nov 6, 2016

@yiisoft/core-developers need your opinions.

Copy link
Member

@dynasource dynasource left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this proof of concept looks good. I approve the split in traits. I agree with @samdark that we should also split the Events & Behaviors. Those two look less entangled as it first seems. Isnt it possible to create BehaviorTrait.php and a EventTrait in which the EventTrait includes BehaviorTrait.

With respect to className(), that one should stay in Component.php with a @deprecated mark. In 2.1 it will be removed.

}

/**
* Returns a list of behaviors that this component should behave as.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see duplication with the @docs of the ComponentInterface. The docs in the traits can be replaced with @inheritdoc tags.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How IDEs would react to that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using @inheritdoc in trait is incorrect, since traits doesn't handle any inheritance. It even break yii2-apidoc as far as I remember.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, most important is not to have duplicated @docs hanging around. Its hard to keep these synchron.

Of course we can choose not to include the tag. Then an approach like https://www.drupal.org/node/2206175 is favorable. So we should have a method doc starting with a 'Implements method...' line and a @see tag to this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gives you completely useless IDE hints:

interface FooInterface {

    /**
     * Interface description.
     */
    public function bar();
}

trait FooTrait {

    /**
     * Implements FooInterface::bar().
     */
    public function bar() {
        echo 'bar';
    }
}

class Foo implements FooInterface {
    use FooTrait;
}

2fb89f21

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said:

and a @see tag to this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't change anything - you still doesn't have any real documentation, only references.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. References are not enough...

Copy link
Member

@samdark samdark Nov 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to re-structure it the way not to duplicate docs and having IDE hints at the same time at all? I don't see a way to do it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clicking on the reference can be made quite obvious

@rob006
Copy link
Contributor Author

rob006 commented Nov 7, 2016

Isnt it possible to create BehaviorTrait.php and a EventTrait in which the EventTrait includes BehaviorTrait.

Doesn't should be opposite? BehaviorTrait includes EventTrait?

With respect to className(), that one should stay in Component.php with a @deprecated mark. In 2.1 it will be removed.

I that case we can't say "Don't use deprecated Object and Component, use new traits" because they're missing one of main features of Object.

@dynasource
Copy link
Member

Doesn't should be opposite? BehaviorTrait includes EventTrait?

The event trait needs php->ensureBehaviors() https://github.com/yiisoft/yii2/pull/12929/files#diff-9c14d263e64e7854e2ef80847ef021b5R397, not the other way around

I that case we can't say "Don't use deprecated Object and Component, use new traits" because they're missing one of main features of Object.

Is that a problem? People can still be advised to use the new traits? And if they want to use the current className(), they still can. Its required for merging in 2.0.x/

@rob006
Copy link
Contributor Author

rob006 commented Nov 8, 2016

The event trait needs php->ensureBehaviors()

Behaviors uses Events: https://github.com/yiisoft/yii2/blob/master/framework/base/Behavior.php .

Is that a problem?

If we are deprecating Object::className() without any alternative, this is a problem.

@dynasource
Copy link
Member

dynasource commented Nov 8, 2016

Behaviors uses Events: https://github.com/yiisoft/yii2/blob/master/framework/base/Behavior.php .

Another entanglement. Lets fix this in another PR and keep this one clean.

If we are deprecating Object::className() without any alternative, this is a problem.

  • it is deprecated in 2.0.x but it still can be used
  • it will be removed in 2.1 and developer have to use php ::class (as discussed in other topic)

@samdark
Copy link
Member

samdark commented Nov 8, 2016

@rob006 isn't PHP 5.6 MyClass::class an alternative to Object::className() for most cases?

* @author Robert Korulczyk <robert@korulczyk.pl>
* @since 2.0.11
*/
trait MutatorsTrait
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am unsure about the naming.
Better be VirtualPropertyTrait

@klimov-paul
Copy link
Member

Alternative approach for #7936

Dispite the change itself can be useful, this PR does not solves issue #7936 anyhow. In case object becomes a PHP reserved word the Yii core code will still crash.

I am sure we have a disscussion for such improvement somewhere - but I can not find it.
Actually related issue is #9748.

@klimov-paul
Copy link
Member

Related disscussion:
#1837 (comment)

@rob006
Copy link
Contributor Author

rob006 commented Nov 8, 2016

@klimov-paul We can't really fix #7936 in 2.0.x, but extracting Object and Component into traits will make it pretty easy in 2.1. The only reason to leave Component and Object in this PR is BC.

@samdark You can't use MyClass::class in PHP 5.4, so this is not an alternative for Yii 2.0. IMO it is better to create another trait with className() implementation than just leave it in Object.

@samdark
Copy link
Member

samdark commented Nov 8, 2016

Right. I agree on another trait for className().

@klimov-paul
Copy link
Member

We can't really fix #7936 in 2.0.x, but extracting Object and Component into traits will make it pretty easy in 2.1

Then issue #7936 should not be placed under 'Fixed issues' section as it produces a delusion.

The only reason to leave Component and Object in this PR is BC.

Disagreed: Component and Object should not be dropped as constant usage of traits and interface implementation is not practical.
Compare:

class Foo extends \yii\base\Object
{
}

with:

class Foo implements \yii\base\Configurable, \yii\base\MutatorsInterface
{
    use \yii\base\ConfigurableTrait;
    use \yii\base\MutatorsTrait;
{
}

@klimov-paul
Copy link
Member

The side effect of this PR is sugnificant increase of entities in class herarchy: additional interfaces and traits. It makes the source code a little bit twisted.

It would be nice to have @qiangxue opinion on this matter - he was the one insisted inheritance tree should be as simple as possible.

Extracting a traits for Object and Component functionality makes sense only for the '3rd party' library, which may use them to integrate some standalone classes into Yii in easy way.
However, in general this initiative was refused as DI supports object creation and configuration via Closure callbacks.

@klimov-paul
Copy link
Member

One more related disscussion: #12021

@dynasource
Copy link
Member

dynasource commented Nov 8, 2016

I agree with not deprecating Component & Object for clean & easy inheritance (with those traits).

I do not agree with keeping the current situation:

  • perhabs its twisted because of the trait & interface combination, but such a combination is something to get used to. Btw., its already getting more and more common in practice.
  • this traits approach makes the software more modular and cleaner organized
  • this traits approach make the software more logical: currently Component is uselessly overriding methods of Object. This should be done cleaner (I now see in this PR that Component is still overriding Object. I expect this is for BC reasons?)
  • like you said, the software will be more flexibile to integrate in 3rd party software

@samdark
Copy link
Member

samdark commented Nov 10, 2016

@klimov-paul no. We won't compose each core class using traits. That's more for user classes which can't inherit from Object but need properties and behaviors at the same time.

@klimov-paul
Copy link
Member

We won't compose each core class using traits. That's more for user classes which can't inherit from Object but need properties and behaviors at the same time.

Then what do you mean by telling:

Classes are still there because we need backwards compatibility.

?

@samdark
Copy link
Member

samdark commented Nov 10, 2016

I've meant that that there will be no changes about core classes in 2.0. In 2.1 for some classes it potentially could make sense to remove all properties and either leave constructor only or create setters without defining properties. In many cases behaviors aren't meant to be attached as well. For these cases one may either not use traits at all or use one of them only.

@rob006
Copy link
Contributor Author

rob006 commented Nov 12, 2016

This is more problematic than I expected. Due some limitations in handling traits and interfaces in PHP 5 (sometimes PHP 5 does not take into account used traits when checking interface implementation, see https://3v4l.org/JWXqX ), we have 3 options:

  1. Do no implement BehaviorsInterface in Component (or don't use interfaces at all, like @klimov-paul requested)
    https://github.com/rob006/yii2-dev/blob/aba6d8b9e83597565efec4023740eed8e2ad787c/framework/base/Component.php#L101 => https://travis-ci.org/rob006/yii2-dev/builds/175317588
  2. Use some ugly workaround in Component
    https://github.com/rob006/yii2-dev/blob/f8ef6ae8c35145f013eb9af03ed8180a78dd84df/framework/base/Component.php#L101 => https://travis-ci.org/rob006/yii2-dev/builds/175317203
  3. Do not extend Component from Object (breaks BC)
    https://github.com/rob006/yii2-dev/blob/955019c90bf95b6a0ff9d6d09260ef1afc7661d5/framework/base/Component.php#L101 => https://travis-ci.org/rob006/yii2-dev/builds/175314851

@rob006 rob006 mentioned this pull request Apr 1, 2017
5 tasks
@cebe cebe modified the milestones: 2.1.x, 2.0.x Jul 14, 2017
@samdark samdark modified the milestones: 2.1.0, 2.1.x Jul 21, 2017
@samdark samdark self-assigned this Jul 21, 2017
@samdark
Copy link
Member

samdark commented Jul 21, 2017

I'd like to have it in 2.1.

@dynasource
Copy link
Member

me too

@cebe
Copy link
Member

cebe commented Jul 22, 2017

I don't like splitting the base classes of all classes in Yii into multiple traits, this will blow up the minimum overhead added by the framework, as instead of loading two classes (Object and Component) it would also need to include a set of traits and interface.

@sergeymakinen
Copy link
Member

I don't think that 2 traits and an interface is a measurable issue in terms of performance. And even 10 traits and interfaces. Especially in a PHP 7 era.

@cebe
Copy link
Member

cebe commented Jul 22, 2017

Its not only performance its more about general complexity of the base. Yii philosophy is to keep things simple and practical. Separating the base class into multiple traits adds complexity in understanding it.
To understand what is going on in Yii you'd have to look into multiple classes, traits and interfaces and put all that together to get the idea of what you are building on, while currently you have two classes and thats it.

What exactly are the practical benefits of such a separation?

@sergeymakinen
Copy link
Member

I was about a performance. My thoughts about a PR usefulness are still controversial.

I love Yii's KISS. But I find VirtualPropertiesTrait useful. Because it provides things are common without a need to inherit BaseObject. EventsInterface and its trait are also handy when you need to accept an object implementing this thing without inheriting. TBH I would implement these changes and that's it.

On the other hand, as far as I understand the whole thing is about breaking inheritance chains. From this point I think this PR is a solid step forward.

@samdark
Copy link
Member

samdark commented Jul 23, 2017

Yes, it's about a step towards removing inheritance chain that's way too long.

@rob006
Copy link
Contributor Author

rob006 commented Jul 23, 2017

What exactly are the practical benefits of such a separation?

  1. Extracting this logic into traits will allow to easily use events/behaviors/virtual attributes in third party classes.
  2. Separating events and behaviors may increase performance in some cases - if you need only events, you can skip behaviors overhead by using only EventsTrait.
  3. At least for me splitting Component into smaller traits focused on single feature, make it easier to read and understand.

@dynasource
Copy link
Member

Perfectly summarized

@cebe
Copy link
Member

cebe commented Jul 24, 2017

Yes, it's about a step towards removing inheritance chain that's way too long.

in current state its making inheritance chain more complex.

Extracting this logic into traits will allow to easily use events/behaviors/virtual attributes in third party classes.

what kind of third party are you thinking of here? Which package would require yiisoft/yii2 just to use these traits?

Separating events and behaviors may increase performance in some cases - if you need only events, you can skip behaviors overhead by using only EventsTrait.

the overhead is marginal and having to decide every time you create a class does not really safe anything, it takes time to make a decision whether to include which kind of trait, and it may limit usage if you later decide to add some behavior. For extensions that means if an extension excluded behaviors you can not inject your functionality and start a discussion with the author instead of using existing functionality that is always there if needed. Having behaviors available by default makes Yii perfectly extensible in many ways by default. I have experienced this when working on HumHub, which is very flexible when writing extensions for it, behaviors allow you to inject a lot of functionality. I'd keep this in by default, arguing about performance here would result in the question whether you'd want to use a framework at all.

@rob006
Copy link
Contributor Author

rob006 commented Jul 24, 2017

what kind of third party are you thinking of here? Which package would require yiisoft/yii2 just to use these traits?

Any yii2 extension that provides integration with any existing non-yii2 library. Instead just wrapping everything you can extend library class, use these traits and interfaces and you will have yii2-like component.

the overhead is marginal and having to decide every time you create a class does not really safe anything, it takes time to make a decision whether to include which kind of trait, and it may limit usage if you later decide to add some behavior.

  1. You already need to make decision whether to use Component, BaseObject or nothing as base for your class.
  2. Component will still provide behaviors and events, and creating events-only class will be more complicated than just extend Component, so it probably will be used only if someone knows what he is doing and really need it. I don't think that framework should force using behaviors in this way.

@rob006
Copy link
Contributor Author

rob006 commented Aug 16, 2017

I'm closing this PR for now since after #14478 it needs to be redone from scratch. Please continue discussion in #9748 and let me know about your decision - I'm interested in to finish this in new PR if the idea gets a green light.

@rob006 rob006 closed this Aug 16, 2017
@rob006 rob006 deleted the replace-object-by-traits branch August 16, 2017 21:24
@cebe cebe removed this from the 2.1.0 milestone Aug 17, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Extract events from Component class into trait
7 participants