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

Missing equalTo() method #4

Closed
beberlei opened this issue Aug 22, 2014 · 30 comments
Closed

Missing equalTo() method #4

beberlei opened this issue Aug 22, 2014 · 30 comments

Comments

@beberlei
Copy link

Sometimes i want to compare enums for equaility, currnetly i have to cast to (string) to make it work. This could be something to add as a default method.

@mnapoli
Copy link
Member

mnapoli commented Aug 23, 2014

Yep that would definitely make sense!

@mnapoli
Copy link
Member

mnapoli commented Jan 31, 2015

See also #8 you can use ==:

if ($enum == MyEnum::FOO()) {
}

So in the end I'm not sure a equalTo() method is necessary?

@mirfilip
Copy link

@mnapoli I was about to write the same yesterday.

@mnapoli
Copy link
Member

mnapoli commented Jan 31, 2015

OK closing for now.

@mnapoli mnapoli closed this as completed Jan 31, 2015
@notfalsedev
Copy link

+1 for a 'equalsTo' method.
It's much easier to use.

public function equalsTo($enum)
{
  return $this == $enum;
}
$enum1 = Enum::KEY_ONE();
$enum2 = Enum::KEY_TWO();

$enum1->equalsTo($enum2); (false)
$enum1->equalsTo($enum1); (true)

@jeremykendall
Copy link
Contributor

I was looking for this functionality last night. Added PR #39 + a test.

@mnapoli
Copy link
Member

mnapoli commented Oct 4, 2016

#39 has been merged, I'll give it a day or two for everyone to review (given how used the library is) and then I'll tag 1.5.0

Thanks @jeremykendall

@mnapoli mnapoli reopened this Oct 4, 2016
@jeremykendall
Copy link
Contributor

@mnapoli You're welcome! Thanks for letting me contribute!

@mirfilip
Copy link

mirfilip commented Oct 5, 2016

@mnapoli @jeremykendall My team and I use this library as a base for our own enum library (a wrapper, you can call it) and we tend to add features that seems necessary for us and maybe doesn't warrant the inclusion to php-enum itself. With that intro, I'll share our insights cause we've discussed "equals in php" problem couple of times:

The need for equals method is most visible to (ex-)Java programmers who wander to php valley. I consider Java functionality of comparing objects a good mechanism and is considered so by Java community in general (afaik, never seen people complaining). I'm talking about equals + hashCode flexibility. This post provides an easy walkthrough on functionality. See this question as an exactly the same question we have here, just in Java world.

The beauty there is "equals defers to ==". That means, if you don't override it, both will work the same way. Also, Java internals use equals method to compare objects which means you can change it's done by overriding equals or hashCode (the latter being the blessed way).

Back to PHP, that's not how it works. PHP internals will almost always use ==. So what? Indeed, there is no difference in standard case:

final class Color extends \MyCLabs\Enum\Enum {
    const RED = 'red';
    const BLUE = 'blue';
}

$red = COLOR::RED();
$blue = COLOR::BLUE();

$anotherRed = COLOR::RED();

// Equality check using == // expected
var_dump($red == $red); // true
var_dump($red == $blue); // false
var_dump($red == $anotherRed); // true

// Equality check using equals() // expected
var_dump($red->equals($red)); // true
var_dump($red->equals($blue)); // false
var_dump($red->equals($anotherRed)); // true

All good, there is no blessed equality checking method, user uses whichever preferred.

Then, user goes and overwrites the equal method ("because reasons") and both methods stop producing the same results. Having that, it's a matter of time for bugs to start cropping in cause the user is accustomed to using both method interchangeably.

There is a simple fix which would make the design of that feature better - declare the equals method final. That way, you can't simply extend this library, make mess and blame the library - if you want some flexibility and you know what you're doing - go with composition.

Maybe I'm being picky now, but that's what I expect from a good enum library in php if it introduces a third way of comparing objects. Not only enum library, the same goes for value objects.

btw. another way of solving that is going full-Java:

abstract class JavaLikeEnum extends \MyCLabs\Enum\Enum {
    final public function equals(\MyCLabs\Enum\Enum $outsider)
    {
        return $this->hashCode() == $outsider->hashCode();
    }

    protected function hashCode() {
        return $this->getValue();
    }
}

if you want to change what equals means, go after hashCode, don't touch equals. Anyhow, both your method and the one I proposed bring an additional comparison method that bring no added functionality, just confusion in the long run.

@mnapoli
Copy link
Member

mnapoli commented Oct 5, 2016

@mirfilip right that's an interesting POV. Making the method final would make sense. And as @Ocramius would say it's easier to remove final than to add it so it's safer to go this way at first.

Thoughts anyone?

@jeremykendall
Copy link
Contributor

I'm always game for final and would be happy to amend the PR.

Awesome input, @mirfilip!

On Wednesday, October 5, 2016, Matthieu Napoli notifications@github.com
wrote:

@mirfilip https://github.com/mirfilip right that's an interesting POV.
Making the method final would make sense. And as @Ocramius
https://github.com/ocramius would say it's easier to remove final than
to add it so it's safer to go this way at first.

Thoughts anyone?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#4 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AARnZet3bTMOy5V59pSijOdzznCr4b1jks5qw9rhgaJpZM4CaGob
.

Sent from Gmail Mobile

@chippyash
Copy link

@mirfilip - Good use of Final. Just shared it with my dev team as an example - thanks.

@mnapoli
Copy link
Member

mnapoli commented Oct 6, 2016

Ok let's do it then, @jeremykendall could you add it?

@mirfilip
Copy link

mirfilip commented Oct 6, 2016

@jeremykendall When you modify the PR, would you mind adding more tests?

As it stands, two cases are tested:

  1. Compare two different enums
  2. Compare the same instances

The missing case is:
3. Compare two different instances but having the same value ($red and $anotherRed from my example)

@jeremykendall
Copy link
Contributor

Thanks, everyone! I'd love to add final and some additional test cases. It might take a few days as I just left on vacation. If someone else wants to make the update in order to have it merged and tagged as quickly as possible, that would be awesome. Otherwise I'll submit the updates as soon as I can.

@jeremykendall
Copy link
Contributor

I couldn't resist. Just submitted PR #40.

@mnapoli
Copy link
Member

mnapoli commented Oct 9, 2016

All is good now, we can close this I'll tag a release

@mnapoli mnapoli closed this as completed Oct 9, 2016
@mnapoli
Copy link
Member

mnapoli commented Oct 9, 2016

1.5.0 🎉

@sebastiandedeyne
Copy link

sebastiandedeyne commented Oct 10, 2016

Gonna have to say -1 on the final addition :/

composer update breaks all of our sites now, since we implemented our own equals method in a class that extends Enum.

Final would be ok if this was a new major version, but as it is it's a breaking change.

@jeremykendall
Copy link
Contributor

composer update breaks all of our sites now, since we implemented our own equals method in a class that extends Enum.

Ugh. I hate that, and I know it's super frustrating.

Final would be ok if this was a new major version, but as it is it's a breaking change.

Although your custom Enum is broken (a very real and very frustrating problem), adding a new method isn't a breaking change, meaning that a minor version bump is the most appropriate choice. From semver.org:

  1. MINOR version when you add functionality in a backwards-compatible manner ...

That said, I'm interested to hear more about your equals implementation and how it differs from what has been added in 1.5.0. Would you be willing to share?

@sebastiandedeyne
Copy link

sebastiandedeyne commented Oct 10, 2016

MINOR version when you add functionality in a backwards-compatible manner

That's the issue, it wasn't added in a backwards compatible manner.

Cannot override final method MyCLabs\Enum\Enum::equals()

There's no difference in functionality, it breaks because of the final keyword.

Don't get me wrong, I welcome the addition of the method to this package, it just shouldn't be final unless you're tagging a new major version.

@chippyash
Copy link

@sebastiandedeyne I may be missing something here. A Major version is required when the change breaks compatibility in the source lib, e.g. an API changes. I'm not sure that adding a new method (with or without final) that may clash with unknown downstream code constitutes a breaking change. After all, that way, we may never get enhancements into the code base. But be interesting to hear other views too.

@mnapoli
Copy link
Member

mnapoli commented Oct 10, 2016

That said, I'm interested to hear more about your equals implementation and how it differs from what has been added in 1.5.0. Would you be willing to share?

I have the same question, final is supposed to prevent overriding the method so I'm curious why (if we exclude the fact that you already defined it before) you would need to override it?

@jeremykendall
Copy link
Contributor

I'm not sure that adding a new method (with or without final) that may clash with unknown downstream code constitutes a breaking change.

That's my take on the semver concern (emphasis added).

@sebastiandedeyne
Copy link

I think you guys are right, semver doesn't seem to consider this a breaking change.

I still don't really agree with final since it increases the chance to break implementations with for no great value.

@mirfilip
Copy link

mirfilip commented Oct 10, 2016

@sebastiandedeyne I feel your pain, but this is not a BC break. If you point composer to such a loose version constraint, that's what you get.

BC break from semver definition treats about functionality that has been added to a library and later is being changed, so that people depending on it encounter a functionality breakage.

There was no equals previously in this library, so addition of a new method is by no means a contract breakage.

If you think about it, it makes perfect sense. I can take a well known framework (say Symfony), pick a popular class, extend it and add all the popular function names I could think of (for discussion sake, let's say it makes sense). Then, at some point, Symfony people introduce the same named function but with different definition (final, different number of arguments, different typehinting, you name it). It's not reasonable to say they would be breaking any contract of Symfony, right?

Anyway, even in your definition final keyword is not the problem, the sole addition of equals method is. The same would happen if a new release added equals with a different number of arguments than your override.

EDIT: I just realized I had missed @chippyash post that clarifies it all. Disregard my long dispute :)

EDIT 2: Just my overview of the addition of equals method: It adds some syntax sugar but no new functionality. That's why I was skeptic at first. Still, @sebastiandedeyne problem proves that users have been adding their own implementation before, which makes inclusion of this method to the "core library" a reasonable decision.

@sebastiandedeyne
Copy link

Makes sense, fair enough :)

@Ocramius
Copy link
Contributor

Adding final IS a breaking API change. You can remove f
final at any time, but adding it requires a major version bump.

On 10 Oct 2016 18:06, "Sebastian De Deyne" notifications@github.com wrote:

I think you guys are right, semver doesn't seem to consider this a
breaking change.

I still don't really agree with final since it increases the chance to
break implementations with for no great value.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#4 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAJakCa-JSokgayTYyuL1McDrdYEzUOxks5qymJugaJpZM4CaGob
.

@jeremykendall
Copy link
Contributor

If adding final to an existing API method, definitely, but what about
adding a new final method? I feel like that's an important distinction.

On Monday, October 10, 2016, Marco Pivetta notifications@github.com wrote:

Adding final IS a breaking API change. You can remove f
final at any time, but adding it requires a major version bump.

On 10 Oct 2016 18:06, "Sebastian De Deyne" <notifications@github.com
javascript:_e(%7B%7D,'cvml','notifications@github.com');> wrote:

I think you guys are right, semver doesn't seem to consider this a
breaking change.

I still don't really agree with final since it increases the chance to
break implementations with for no great value.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#4 (comment),
or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAJakCa-
JSokgayTYyuL1McDrdYEzUOxks5qymJugaJpZM4CaGob>
.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#4 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AARnZQBl8dzqSj0Nl3oq1bB7NFWSu3SFks5qyttjgaJpZM4CaGob
.

Sent from Gmail Mobile

@Ocramius
Copy link
Contributor

Yeah, good point, new method is surely not a breakage, though the
possibility of somebody overriding it by accident always exists

On 11 Oct 2016 02:44, "Jeremy Kendall" notifications@github.com wrote:

If adding final to an existing API method, definitely, but what about
adding a new final method? I feel like that's an important distinction.

On Monday, October 10, 2016, Marco Pivetta notifications@github.com
wrote:

Adding final IS a breaking API change. You can remove f
final at any time, but adding it requires a major version bump.

On 10 Oct 2016 18:06, "Sebastian De Deyne" <notifications@github.com
javascript:_e(%7B%7D,'cvml','notifications@github.com');> wrote:

I think you guys are right, semver doesn't seem to consider this a
breaking change.

I still don't really agree with final since it increases the chance to
break implementations with for no great value.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#4 (comment),
or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAJakCa-
JSokgayTYyuL1McDrdYEzUOxks5qymJugaJpZM4CaGob>
.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#4 (comment),
or mute
the thread
<https://github.com/notifications/unsubscribe-auth/
AARnZQBl8dzqSj0Nl3oq1bB7NFWSu3SFks5qyttjgaJpZM4CaGob>
.

Sent from Gmail Mobile


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#4 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAJakM8xbxdeEgPKs7ay0jQHAkUdWTHSks5qytv6gaJpZM4CaGob
.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants