Re: [PHP-DEV] Class Re-implementation Mechanism

2023-02-21 Thread someniatko
>
>
>> So it's mostly a syntactic sugar that allows defining an interface for
> the current class that is already implemented by the current class.
>

Yeah, while answering to you, I've actually came to the same conclusion.

>

> The interface name is the same as the class name and based on the context,
> you either use one or the other.
>

I think we should distinguish this thing from an interface though.

There are some places where this could get in conflict like (new
> ReflectionClass(A::class))->isInterface(). But also internally things might
> be difficult to sort out to know what to use one or the other.
>

Yeah, I also thought about reflection, exactly about this method. I believe
it's better not to lie, and make isInterface() return false. However,
another method should be added, e.g. isClassInterface() - which will return
true only in case of `class interface`, or, maybe a more general one like
isImplementable(), which will also return true for interfaces.


> To have a perfect syntax-sugar, why not also allow the interface name to
> be specified?
> Something like:
> final class interface(ServiceInterface) Service {
> public function method1() {}
> protected function method2() {}
> }
>
> that would generate:
> interface ServiceInterface {
> public function method1();
> }
> final class Service implements ServiceInterface {
> public function method1() {}
> protected function method2() {}
> }
>

Well that goes against my initial concern of not introducing additional
entities to the system. However it indeed reduces the amount of code to
write and is more "transparent" type of syntaxic sugar, like the promoted
properties in constructors - this feature also translates to PHP code. If
my suggestion ever becomes an RFC, this way of implementing it may become a
separate vote I suppose.


Regards,
Illia / someniatko


Re: [PHP-DEV] Class Re-implementation Mechanism

2023-02-21 Thread Bruce Weirdan
On Tue, Feb 21, 2023 at 7:53 AM someniatko  wrote:

> We want to write some tests for the Service class, but we don't want
> to use a real SomeDependency instance
> during tests. A common approach is to either extract an interface
> (JUST to make it testable), or to drop the
> `final` keyword and allow extending the class.
>
> Both approaches have their flaws:
>  - extracting an interface unnecessarily complicates the system, where
> only one "real" implementation of an interface is assumed.
>  - dropping the `final` keyword allows for the rabbit-hole of
> inheritance abuse, like greatly described in this article:
> https://front-line-php.com/object-oriented

There's another approach that requires neither: removing `final`
during class loading, for tests only.

See https://github.com/dg/bypass-finals (and
https://github.com/nunomaduro/mock-final-classes for plug'n'play
integration with PHPUnit).

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] Class Re-implementation Mechanism

2023-02-21 Thread Alexandru Pătrănescu
On Tue, Feb 21, 2023 at 3:30 PM someniatko  wrote:

> > This sounds interesting but it breaks some expectations.
> >
> > Interesting because you can have any class act as an interface for other
> classes with the interface being built up of any public properties or
> method that exists on that class.
>
> In my original suggestion, it's not like any class, but only those you
> specifically mark as `class interface`, not just `class`. Closer to
> the end of my idea I also suggest that maybe it's worth it to allow
> implementing, like interfaces, ANY classes, not only those
> specifically marked with the new syntax, but this indeed brings up
> some issues.
>
> > The expectation it breaks is that if you have a final class, whenever
> you use it, you expect some specific implementation exists on it.
> > And this would break the expectation and in real life someone might pass
> a totally different implementation.
> > Will not break generic LSP but still will break some expectations.
>
> That's why a new syntax is brought up. This expectation might indeed
> break if we allow to reimplement any existing final class, instead of
> requiring a manual opt-in.
>
>
Understood, thanks for explaining.
So it's mostly a syntactic sugar that allows defining an interface for the
current class that is already implemented by the current class.

The interface name is the same as the class name and based on the context,
you either use one or the other.
There are some places where this could get in conflict like (new
ReflectionClass(A::class))->isInterface(). But also internally things might
be difficult to sort out to know what to use one or the other.

To have a perfect syntax-sugar, why not also allow the interface name to be
specified?
Something like:
final class interface(ServiceInterface) Service {
public function method1() {}
protected function method2() {}
}

that would generate:
interface ServiceInterface {
public function method1();
}
final class Service implements ServiceInterface {
public function method1() {}
protected function method2() {}
}

As explained previously (and now I got that), autoloading will not work in
this case. But some classmaps can be configured for composer or you could
just force class loading with class_exists() in limited places where this
is needed (in tests).

Alex


Re: [PHP-DEV] Class Re-implementation Mechanism

2023-02-21 Thread someniatko
> This sounds interesting but it breaks some expectations.
>
> Interesting because you can have any class act as an interface for other 
> classes with the interface being built up of any public properties or method 
> that exists on that class.

In my original suggestion, it's not like any class, but only those you
specifically mark as `class interface`, not just `class`. Closer to
the end of my idea I also suggest that maybe it's worth it to allow
implementing, like interfaces, ANY classes, not only those
specifically marked with the new syntax, but this indeed brings up
some issues.


> Ok, maybe not any class but just a final class.

I see no issue there of allowing to reimplement the interface of a
non-final class. This still is better than extending from it IMO. But
we _probably_ should prohibit it from abstract classes.


> And also maybe just a final class that doesn't yet implement an interface.

Also, here is no conflict as well. When reimplementing the interface
of the class, you can treat it as a normal interface - which can
extend from other interfaces.


> The expectation it breaks is that if you have a final class, whenever you use 
> it, you expect some specific implementation exists on it.
> And this would break the expectation and in real life someone might pass a 
> totally different implementation.
> Will not break generic LSP but still will break some expectations.

That's why a new syntax is brought up. This expectation might indeed
break if we allow to reimplement any existing final class, instead of
requiring a manual opt-in.

(sorry, this email is sent to you the second time, Alexandru, because
I forgot to reply to the maillist as well)

Regards,
Illia / somenitko

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] Class Re-implementation Mechanism

2023-02-21 Thread Stephen Reay



> On 21 Feb 2023, at 18:52, someniatko  wrote:
> 
> Hi again, internals
> 
> My marathon of some crazy ideas continues :D, with less crazy one this time.
> 
> 
> ## Idea
> 
> Allow "reimplementing" the non-static public API (that is public
> properties and methods, excluding constructor) of a class by other
> classes like this:
> 
> ```php
> final class interface A {
>public string $s;
> 
>public function __construct(string $s) { $this->s = $s; }
> 
>public static function fromInt(int $i): self { return new
> self((string) $i); }
> 
>public function foo(): int { return 42; }
> }
> 
> final class B implements A {
>public string $s = 'hello';
> 
>public function foo(): int { return 69; }
> }
> 
> function usesA(A $param): void {}
> 
> usesA(new B); // this works
> ```
> 
> 
> ## Explanation
> 
> Consider there is a class like this:
> 
> ```php
> final class Service {
>public function __construct(private SomeDependency $dependency) {}
>// ...
> }
> 
> final class SomeDependency {
>// ...
> }
> ```
> 
> We want to write some tests for the Service class, but we don't want
> to use a real SomeDependency instance
> during tests. A common approach is to either extract an interface
> (JUST to make it testable), or to drop the
> `final` keyword and allow extending the class.
> 
> Both approaches have their flaws:
> - extracting an interface unnecessarily complicates the system, where
> only one "real" implementation of an interface is assumed.
> - dropping the `final` keyword allows for the rabbit-hole of
> inheritance abuse, like greatly described in this article:
> https://front-line-php.com/object-oriented
> 
> I believe I came up with a better idea: what if we could leave both
> benefits of prohibiting the inheritance abuse and also allow not to
> clutter our namespace with excess entities like interfaces? I hereby
> suggest to combine the responsibilities of a class and an interface
> into one thing like that:
> 
> ```php
> final class interface C {}
> final class D implements C {}
> ```
> 
> Now other classes can "implement" this class as well. Introduction of
> the new syntax (`class interface`) also solves BC problem - if you
> want to forbid your classes to be reimplemented whatsoever, you can
> still stick to the `final class` syntax. Although it is also possible
> to allow "reimplementing" ANY class, then new syntax is not needed -
> but some library writers like Marco Pivetta could be sad about that I
> suppose.
> 
> Soo..., what do you think? Could this be a valuable addition to the language?
> 
> Regards,
> Illia / someniatko
> 
> -- 
> PHP Internals - PHP Runtime Development Mailing List
> To unsubscribe, visit: https://www.php.net/unsub.php
> 

Hi

It’s possible I’m missing something here, because it’s hard to tell which parts 
are new syntax you’re suggesting, and which parts are unfortunate typos…

Can you explain how using a regular interface (or even an abstract base class 
if you prefer) doesn’t already achieve what you want?



Cheers

Stephen 

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] Class Re-implementation Mechanism

2023-02-21 Thread Alexandru Pătrănescu
Hi Someniatko,

On Tue, Feb 21, 2023 at 1:52 PM someniatko  wrote:

> Hi again, internals
>
> My marathon of some crazy ideas continues :D, with less crazy one this
> time.
>
>
> ## Idea
>
> Allow "reimplementing" the non-static public API (that is public
> properties and methods, excluding constructor) of a class by other
> classes like this:
>
> ```php
> final class interface A {
> public string $s;
>
> public function __construct(string $s) { $this->s = $s; }
>
> public static function fromInt(int $i): self { return new
> self((string) $i); }
>
> public function foo(): int { return 42; }
> }
>
> final class B implements A {
> public string $s = 'hello';
>
> public function foo(): int { return 69; }
> }
>
> function usesA(A $param): void {}
>
> usesA(new B); // this works
> ```
>
>
> ## Explanation
>
> Consider there is a class like this:
>
> ```php
> final class Service {
> public function __construct(private SomeDependency $dependency) {}
> // ...
> }
>
> final class SomeDependency {
> // ...
> }
> ```
>
> We want to write some tests for the Service class, but we don't want
> to use a real SomeDependency instance
> during tests. A common approach is to either extract an interface
> (JUST to make it testable), or to drop the
> `final` keyword and allow extending the class.
>
> Both approaches have their flaws:
>  - extracting an interface unnecessarily complicates the system, where
> only one "real" implementation of an interface is assumed.
>  - dropping the `final` keyword allows for the rabbit-hole of
> inheritance abuse, like greatly described in this article:
> https://front-line-php.com/object-oriented
>
> I believe I came up with a better idea: what if we could leave both
> benefits of prohibiting the inheritance abuse and also allow not to
> clutter our namespace with excess entities like interfaces? I hereby
> suggest to combine the responsibilities of a class and an interface
> into one thing like that:
>
> ```php
> final class interface C {}
> final class D implements C {}
> ```
>
> Now other classes can "implement" this class as well. Introduction of
> the new syntax (`class interface`) also solves BC problem - if you
> want to forbid your classes to be reimplemented whatsoever, you can
> still stick to the `final class` syntax. Although it is also possible
> to allow "reimplementing" ANY class, then new syntax is not needed -
> but some library writers like Marco Pivetta could be sad about that I
> suppose.
>
> Soo..., what do you think? Could this be a valuable addition to the
> language?
>

This sounds interesting but it breaks some expectations.

Interesting because you can have any class act as an interface for other
classes with the interface being built up of any public properties or
method that exists on that class.
Ok, maybe not any class but just a final class. And also maybe just a final
class that doesn't yet implement an interface.

The expectation it breaks is that if you have a final class, whenever you
use it, you expect some specific implementation exists on it.
And this would break the expectation and in real life someone might pass a
totally different implementation.
Will not break generic LSP but still will break some expectations.



I believe other options should be followed that are more straightforward:

If it's your internal code not shared with others, you can easily extract
an interface at any point where you need to inject another instance (when
testing).
Also, if it's internal code, you can just not mark the class as final.

If it's a library code you use or share with others, a class should not be
marked as final if there is no interface they implement and that should be
fixed by extracting an interface.

If it's a value object, you don't need to mock it, wherever it exists.

Regards,
Alex


Re: [PHP-DEV] Class Re-implementation Mechanism

2023-02-21 Thread Robert Landers
On Tue, Feb 21, 2023 at 12:52 PM someniatko  wrote:
>
> Hi again, internals
>
> My marathon of some crazy ideas continues :D, with less crazy one this time.
>
>
> ## Idea
>
> Allow "reimplementing" the non-static public API (that is public
> properties and methods, excluding constructor) of a class by other
> classes like this:
>
> ```php
> final class interface A {
> public string $s;
>
> public function __construct(string $s) { $this->s = $s; }
>
> public static function fromInt(int $i): self { return new
> self((string) $i); }
>
> public function foo(): int { return 42; }
> }
>
> final class B implements A {
> public string $s = 'hello';
>
> public function foo(): int { return 69; }
> }
>
> function usesA(A $param): void {}
>
> usesA(new B); // this works
> ```
>
>
> ## Explanation
>
> Consider there is a class like this:
>
> ```php
> final class Service {
> public function __construct(private SomeDependency $dependency) {}
> // ...
> }
>
> final class SomeDependency {
> // ...
> }
> ```
>
> We want to write some tests for the Service class, but we don't want
> to use a real SomeDependency instance
> during tests. A common approach is to either extract an interface
> (JUST to make it testable), or to drop the
> `final` keyword and allow extending the class.
>
> Both approaches have their flaws:
>  - extracting an interface unnecessarily complicates the system, where
> only one "real" implementation of an interface is assumed.
>  - dropping the `final` keyword allows for the rabbit-hole of
> inheritance abuse, like greatly described in this article:
> https://front-line-php.com/object-oriented
>
> I believe I came up with a better idea: what if we could leave both
> benefits of prohibiting the inheritance abuse and also allow not to
> clutter our namespace with excess entities like interfaces? I hereby
> suggest to combine the responsibilities of a class and an interface
> into one thing like that:
>
> ```php
> final class interface C {}
> final class D implements C {}
> ```
>
> Now other classes can "implement" this class as well. Introduction of
> the new syntax (`class interface`) also solves BC problem - if you
> want to forbid your classes to be reimplemented whatsoever, you can
> still stick to the `final class` syntax. Although it is also possible
> to allow "reimplementing" ANY class, then new syntax is not needed -
> but some library writers like Marco Pivetta could be sad about that I
> suppose.
>
> Soo..., what do you think? Could this be a valuable addition to the language?
>
> Regards,
> Illia / someniatko
>
> --
> PHP Internals - PHP Runtime Development Mailing List
> To unsubscribe, visit: https://www.php.net/unsub.php
>

This reminds me a lot of C#'s "extension methods" which are quite
useful. If PHP had something like that, you could do some really nice
things:

class BMock {
  public static function bar from A(): int { return 69; }
}

$a = new A(123);
echo $a->bar(); // output: 69

class BReal {
  public static function bar from A(): int { return $self->foo(); }
}

// error A::bar() already defined on BMock

where `from A` is syntax sugar for `public function bar(A $self) {}`
and trying `$a->bar()` is simply syntax sugar for `BMock::bar($a);`

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php



Re: [PHP-DEV] Class Re-implementation Mechanism

2023-02-21 Thread Deleu
On Tue, Feb 21, 2023, 8:52 AM someniatko  wrote:

> Hi again, internals
>
> My marathon of some crazy ideas continues :D, with less crazy one this
> time.
>
>
> ## Idea
>
> Allow "reimplementing" the non-static public API (that is public
> properties and methods, excluding constructor) of a class by other
> classes like this:
>
> ```php
> final class interface A {
> public string $s;
>
> public function __construct(string $s) { $this->s = $s; }
>
> public static function fromInt(int $i): self { return new
> self((string) $i); }
>
> public function foo(): int { return 42; }
> }
>
> final class B implements A {
> public string $s = 'hello';
>
> public function foo(): int { return 69; }
> }
>
> function usesA(A $param): void {}
>
> usesA(new B); // this works
> ```
>
>
> ## Explanation
>
> Consider there is a class like this:
>
> ```php
> final class Service {
> public function __construct(private SomeDependency $dependency) {}
> // ...
> }
>
> final class SomeDependency {
> // ...
> }
> ```
>
> We want to write some tests for the Service class, but we don't want
> to use a real SomeDependency instance
> during tests. A common approach is to either extract an interface
> (JUST to make it testable), or to drop the
> `final` keyword and allow extending the class.
>
> Both approaches have their flaws:
>  - extracting an interface unnecessarily complicates the system, where
> only one "real" implementation of an interface is assumed.
>  - dropping the `final` keyword allows for the rabbit-hole of
> inheritance abuse, like greatly described in this article:
> https://front-line-php.com/object-oriented
>
> I believe I came up with a better idea: what if we could leave both
> benefits of prohibiting the inheritance abuse and also allow not to
> clutter our namespace with excess entities like interfaces? I hereby
> suggest to combine the responsibilities of a class and an interface
> into one thing like that:
>
> ```php
> final class interface C {}
> final class D implements C {}
> ```
>
> Now other classes can "implement" this class as well. Introduction of
> the new syntax (`class interface`) also solves BC problem - if you
> want to forbid your classes to be reimplemented whatsoever, you can
> still stick to the `final class` syntax. Although it is also possible
> to allow "reimplementing" ANY class, then new syntax is not needed -
> but some library writers like Marco Pivetta could be sad about that I
> suppose.
>
> Soo..., what do you think? Could this be a valuable addition to the
> language?
>
> Regards,
> Illia / someniatko
>
> --
> PHP Internals - PHP Runtime Development Mailing List
> To unsubscribe, visit: https://www.php.net/unsub.php



Although I undeterstand and agree with the annoyance of both limitations
presented, I think one important factor that makes this an issue lies with
PSR-4 and autoloading. If you could declare the interface and the
implementation in the same file, as you do with Typescript, the drawback of
extracting an interface for testing would be greatly diminished.
Unfortunately undoing PSR-4 shortcomings and getting something baked into
Composer or PHP to address this seems fairly challenging.


[PHP-DEV] Class Re-implementation Mechanism

2023-02-21 Thread someniatko
Hi again, internals

My marathon of some crazy ideas continues :D, with less crazy one this time.


## Idea

Allow "reimplementing" the non-static public API (that is public
properties and methods, excluding constructor) of a class by other
classes like this:

```php
final class interface A {
public string $s;

public function __construct(string $s) { $this->s = $s; }

public static function fromInt(int $i): self { return new
self((string) $i); }

public function foo(): int { return 42; }
}

final class B implements A {
public string $s = 'hello';

public function foo(): int { return 69; }
}

function usesA(A $param): void {}

usesA(new B); // this works
```


## Explanation

Consider there is a class like this:

```php
final class Service {
public function __construct(private SomeDependency $dependency) {}
// ...
}

final class SomeDependency {
// ...
}
```

We want to write some tests for the Service class, but we don't want
to use a real SomeDependency instance
during tests. A common approach is to either extract an interface
(JUST to make it testable), or to drop the
`final` keyword and allow extending the class.

Both approaches have their flaws:
 - extracting an interface unnecessarily complicates the system, where
only one "real" implementation of an interface is assumed.
 - dropping the `final` keyword allows for the rabbit-hole of
inheritance abuse, like greatly described in this article:
https://front-line-php.com/object-oriented

I believe I came up with a better idea: what if we could leave both
benefits of prohibiting the inheritance abuse and also allow not to
clutter our namespace with excess entities like interfaces? I hereby
suggest to combine the responsibilities of a class and an interface
into one thing like that:

```php
final class interface C {}
final class D implements C {}
```

Now other classes can "implement" this class as well. Introduction of
the new syntax (`class interface`) also solves BC problem - if you
want to forbid your classes to be reimplemented whatsoever, you can
still stick to the `final class` syntax. Although it is also possible
to allow "reimplementing" ANY class, then new syntax is not needed -
but some library writers like Marco Pivetta could be sad about that I
suppose.

Soo..., what do you think? Could this be a valuable addition to the language?

Regards,
Illia / someniatko

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php