Re: [PHP-DEV] [RFC][Discussion] Why can constructors violate LSP?

2023-11-24 Thread Robert Landers
On Fri, Nov 24, 2023 at 2:30 PM Deleu  wrote:
>>
>> Hey Mike,
>>
>> Using a static method to enforce LSP when it should have been enforced
>> in the first place, merely proves my point that it violates LSP. I
>> don't know how else to spell it out, I guess we can try this one next:
>>
>> class A {
>> final public static function foo() {
>> return new static();
>> }
>> }
>>
>> class B extends A {
>> public function __construct(private string $name) {}
>> }
>>
>> $a = B::foo();
>>
>> It just plainly violates LSP and you can't write safe code that
>> touches constructors. I don't know how else to spell this out.
>
>
> Hey Robert, as we've established we may not agree on this subject and I 
> really don't want to sound like I want to convince you, so feel free to 
> ignore my email or reply that this isn't helping. My goal here is merely to 
> try and address what I think the error is in case it gives you some clarity.
> -
>
> In this snippet, you're using `new static()` which again is something 
> PHP-specific and not 100% OOP definition. PHP has a friendly syntax to help 
> you figure out what the class name is during runtime, this again is metadata. 
> With these tools, I believe you are able to accomplish what you want by 
> explicitly adding the constructor to your public API.
>
> ```
> abstract class Template
> {
> abstract public function __construct(string 
> $explicitlyDefinitionOfPublicAPI);
> }
>
> class A extends Template {
> public function __construct(string|null $ignore) {}
>
> final public static function foo()
> {
> return new static('parameter defined from A');
> }
> }
>
> class B extends A {
> public function __construct(public string $name) {}
> }
>
> $b = B::foo('test');
> var_dump($b->name);
> ```
>
> Let's break this down. The `Template` class uses the Abstract Template 
> pattern to define a public interface of the inheritance chain. As such, it 
> offers the ability to opt-in to something that isn't standard: The 
> Constructor method being part of a class Public API.
>
> Two things happen from this. We are forced to implement a constructor on 
> class A otherwise we get:
> Fatal error: Class A contains 1 abstract method and must therefore be 
> declared abstract or implement the remaining methods (Template::__construct) 
> in /in/7qX9j on line 8
>
> And we inherit the Constructor from A in B or we need to override it. My 
> conclusion here is that 1) if you know what you're doing and 2) you want to 
> make a constructor's object part of your public API, and therefore, respect 
> LSP, you are free to do so.
>
> - Why shouldn't this be the default behavior in PHP?
> Let's ignore for a second 28 years of breaking change and focus on the OOP 
> principle
>
>
> ```
> 
> abstract class Queue
> {
>
> final public function serialize(array $message): string
> {
> return json_encode($message);
> }
>
> abstract public function push(array $message): void;
> }
>
> final class SqsQueue extends Queue
> {
> public function __constructor(private \Aws\Sqs\Client $client, private 
> string $queue) {}
>
> public function push(array $message): void
> {
> $data = $this->serialize($message);
>
> $client->sendMessage([
> 'QueueUrl' => $this->queue,
> 'Message' => $data,
> ]);
> }
> }
>
> final class RedisQueue extends Queue
> {
> public function __constructor(private 
> \Illuminate\Redis\Connectors\PhpRedisConnector $connector, private array 
> $configuration, private array $options) {}
>
> public function push(array $message): void
> {
> $connection = $this->connector->connect($this->configuration, 
> $this->options);
>
> $data = $this->serialize($message);
>
> $command = "redis.call('rpush', KEYS[1], ARGV[1])";
>
> $connection->eval($command, 1, 'queues:my-queue', $data);
> }
> }
> ```
>
> This is a text-book case of LSP. A class that expects to receive a `Queue` 
> object can freely use `push()` in a consistent and predictable manner, 
> allowing substitution as it sees fit. The object constructor is exempt from 
> LSP because it is the implementation detail of a particular class.
> RedisQueue needs to be able to communicate with Redis in order to provide a 
> queueing capability.
> SqsQueue needs to be able to communicate with AWS SQS in order to provide a 
> queuing capability.
>
> They have different dependencies/configurations and they wouldn't be able to 
> perform their capabilities if the language forced their constructor to follow 
> a single compatible signature.
>
> -
>
> In conclusion, I think PHP has the best of both worlds. We get little helpers 
> to accommodate how OOP looks like in a dynamic script language (i.e. new 
> static()) and we have a fully functioning LSP that allows you to take 
> advantage of it however you see fit. The Queue example goes to show why 
> having a constructor as part of 

Re: [PHP-DEV] [RFC][Discussion] Why can constructors violate LSP?

2023-11-24 Thread Deleu
>
> Hey Mike,
>
> Using a static method to enforce LSP when it should have been enforced
> in the first place, merely proves my point that it violates LSP. I
> don't know how else to spell it out, I guess we can try this one next:
>
> class A {
> final public static function foo() {
> return new static();
> }
> }
>
> class B extends A {
> public function __construct(private string $name) {}
> }
>
> $a = B::foo();
>
> It just plainly violates LSP and you can't write safe code that
> touches constructors. I don't know how else to spell this out.
>

Hey Robert, as we've established we may not agree on this subject and I
really don't want to sound like I want to convince you, so feel free to
ignore my email or reply that this isn't helping. My goal here is merely to
try and address what I think the error is in case it gives you some clarity.
-

In this snippet, you're using `new static()` which again is something
PHP-specific and not 100% OOP definition. PHP has a friendly syntax to help
you figure out what the class name is during runtime, this again is
metadata. With these tools, I believe you are able to accomplish what you
want by explicitly adding the constructor to your public API.

```
abstract class Template
{
abstract public function __construct(string
$explicitlyDefinitionOfPublicAPI);
}

class A extends Template {
public function __construct(string|null $ignore) {}

final public static function foo()
{
return new static('parameter defined from A');
}
}

class B extends A {
public function __construct(public string $name) {}
}

$b = B::foo('test');
var_dump($b->name);
```

Let's break this down. The `Template` class uses the Abstract Template
pattern to define a public interface of the inheritance chain. As such, it
offers the ability to opt-in to something that isn't standard: The
Constructor method being part of a class Public API.

Two things happen from this. We are forced to implement a constructor on
class A otherwise we get:
Fatal error: Class A contains 1 abstract method and must therefore be
declared abstract or implement the remaining methods
(Template::__construct) in /in/7qX9j on line 8

And we inherit the Constructor from A in B or we need to override it. My
conclusion here is that 1) if you know what you're doing and 2) you want to
make a constructor's object part of your public API, and therefore, respect
LSP, you are free to do so.

- Why shouldn't this be the default behavior in PHP?
Let's ignore for a second 28 years of breaking change and focus on the OOP
principle


```
serialize($message);

$client->sendMessage([
'QueueUrl' => $this->queue,
'Message' => $data,
]);
}
}

final class RedisQueue extends Queue
{
public function __constructor(private
\Illuminate\Redis\Connectors\PhpRedisConnector $connector, private array
$configuration, private array $options) {}

public function push(array $message): void
{
$connection = $this->connector->connect($this->configuration,
$this->options);

$data = $this->serialize($message);

$command = "redis.call('rpush', KEYS[1], ARGV[1])";

$connection->eval($command, 1, 'queues:my-queue', $data);
}
}
```

This is a text-book case of LSP. A class that expects to receive a `Queue`
object can freely use `push()` in a consistent and predictable manner,
allowing substitution as it sees fit. The object constructor is exempt from
LSP because it is the implementation detail of a particular class.
RedisQueue needs to be able to communicate with Redis in order to provide a
queueing capability.
SqsQueue needs to be able to communicate with AWS SQS in order to provide a
queuing capability.

They have different dependencies/configurations and they wouldn't be able
to perform their capabilities if the language forced their constructor to
follow a single compatible signature.

-

In conclusion, I think PHP has the best of both worlds. We get little
helpers to accommodate how OOP looks like in a dynamic script language
(i.e. new static()) and we have a fully functioning LSP that allows you to
take advantage of it however you see fit. The Queue example goes to show
why having a constructor as part of the public API of a class hierarchy
would be extremely bad, but PHP is nice enough to let you opt-in to it if
you have reasons to force a class hierarchy to have a single dependency
signature.


-- 
Marco Deleu


Re: [PHP-DEV] [RFC][Discussion] Why can constructors violate LSP?

2023-11-24 Thread Dennis Birkholz

Hi Robert,

Am 23.11.23 um 21:31 schrieb Robert Landers:

However, we can make certain methods private anyway, namely,
constructors (I haven't gone hunting for other built-in methods yet).
This is perfectly allowed:

class P {
 public function __construct($name = 'waldo') {
 echo "hello $name\n";
 }
}

class C extends P {
 private function __construct($name = 'world') {
 parent::__construct($name);
 echo "goodbye $name\n";
 }
}


you can enforce a public constructor by using an interface:

```
interface X {
    public function __construct (int $foo);
}

class A implements X {
    public function __construct (int $foo) {
    echo __CLASS__ . ": $foo\n";
    }
}

class B extends A {
    private function __construct (int $foo) {
    echo __CLASS__ . ": $foo\n";
    }
}
```

Which will give you the following error:
PHP Fatal error:  Access level to B::__construct() must be public (as in 
class X) in test.php on line 14


That way you put the definition of the constructor into the contract.

Greets
Dennis


Re: [PHP-DEV] [RFC][Discussion] Why can constructors violate LSP?

2023-11-24 Thread Robert Landers
On Fri, Nov 24, 2023 at 12:46 PM Mark Trapp  wrote:
>
> On Thu, Nov 23, 2023 at 12:31 Robert Landers  wrote:
> >
> > Hello Internals,
> >
> > As you may know, an inherited method cannot reduce the visibility of
> > an overridden method. For example, this results in a fatal error
> > during compilation:
> >
> > class P {
> > public function hello($name =3D 'world') {
> > echo "hello $name\n";
> > }
> > }
> >
> > class C extends P {
> > private function hello($name =3D 'world') {
> > parent::hello($name);
> > echo "goodbye $name\n";
> > }
> > }
> >
> > However, we can make certain methods private anyway, namely,
> > constructors (I haven't gone hunting for other built-in methods yet).
> > This is perfectly allowed:
> >
> > class P {
> > public function __construct($name =3D 'waldo') {
> > echo "hello $name\n";
> > }
> > }
> >
> > class C extends P {
> > private function __construct($name =3D 'world') {
> > parent::__construct($name);
> > echo "goodbye $name\n";
> > }
> > }
> >
> > To my somewhat trained eye, this appears to violate the Liskov
> > Substitution Principle, for example, this now can have hidden errors:
> >
> > function create(P $class) {
> > return new (get_class($class))();
> > }
> >
> > proven by:
> >
> > $c =3D (new ReflectionClass(C::class))
> >   ->newInstanceWithoutConstructor();
> >
> > create($c);
> >
> > Even though we thought we knew that the constructor was declared public.
> >
> > I'd like to propose an RFC to enforce the covariance of constructors
> > (just like is done for other methods), to take effect in PHP 9, with a
> > deprecation notice in 8.3.x.
> >
> > I'm more than happy to implement it.
> >
> > Does anyone feel strongly about this one way or the other?
> >
> > Robert Landers
> > Software Engineer
> > Utrecht NL
>
> (Apologies: resending because I used the wrong email address to send
> to the list)
>
> Hi Robert,
>
> Liskov and Wing explicitly exempted constructors in their original
> paper describing the principle:
> http://reports-archive.adm.cs.cmu.edu/anon/1999/CMU-CS-99-156.pdf
>
> > 4.1 Type Specifications
> >
> > A type specification includes the following information:
> >
> > - The type's name
> > - A description of the type's value space
> > - A definition of the type's invariant and history properties
> > - For each of the type's methods:
> > - Its name;
> > - Its signature including signaled exceptions;
> > - Its behavior in terms of pre-conditions and post-conditions.
> >
> > Note that the creators are missing. Omitting creators allows subtypes to =
> provide different creators than their supertypes. In addition, omitting cre=
> ators makes it easy for a type to have multiple implementations, allows new=
>  creators to be added later, and re ects common usage: for example, Java in=
> terfaces and virtual types provide no way for users to create objects of th=
> e type.
>
> (Creators is Liskov's word for constructors, which they later define
> in the paper.)
>
> Hope that helps.
>
> - Mark Trapp

Thanks Mark,

That does help quite a bit! Thanks!

I might think they are wrong, but if so, I'll probably need to write a
paper and prove it before I come back. :D

Cheers!

Rob

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



Re: [PHP-DEV] [RFC][Discussion] Why can constructors violate LSP?

2023-11-24 Thread Mark Trapp
On Thu, Nov 23, 2023 at 12:31 Robert Landers  wrote:
>
> Hello Internals,
>
> As you may know, an inherited method cannot reduce the visibility of
> an overridden method. For example, this results in a fatal error
> during compilation:
>
> class P {
> public function hello($name =3D 'world') {
> echo "hello $name\n";
> }
> }
>
> class C extends P {
> private function hello($name =3D 'world') {
> parent::hello($name);
> echo "goodbye $name\n";
> }
> }
>
> However, we can make certain methods private anyway, namely,
> constructors (I haven't gone hunting for other built-in methods yet).
> This is perfectly allowed:
>
> class P {
> public function __construct($name =3D 'waldo') {
> echo "hello $name\n";
> }
> }
>
> class C extends P {
> private function __construct($name =3D 'world') {
> parent::__construct($name);
> echo "goodbye $name\n";
> }
> }
>
> To my somewhat trained eye, this appears to violate the Liskov
> Substitution Principle, for example, this now can have hidden errors:
>
> function create(P $class) {
> return new (get_class($class))();
> }
>
> proven by:
>
> $c =3D (new ReflectionClass(C::class))
>   ->newInstanceWithoutConstructor();
>
> create($c);
>
> Even though we thought we knew that the constructor was declared public.
>
> I'd like to propose an RFC to enforce the covariance of constructors
> (just like is done for other methods), to take effect in PHP 9, with a
> deprecation notice in 8.3.x.
>
> I'm more than happy to implement it.
>
> Does anyone feel strongly about this one way or the other?
>
> Robert Landers
> Software Engineer
> Utrecht NL

(Apologies: resending because I used the wrong email address to send
to the list)

Hi Robert,

Liskov and Wing explicitly exempted constructors in their original
paper describing the principle:
http://reports-archive.adm.cs.cmu.edu/anon/1999/CMU-CS-99-156.pdf

> 4.1 Type Specifications
>
> A type specification includes the following information:
>
> - The type's name
> - A description of the type's value space
> - A definition of the type's invariant and history properties
> - For each of the type's methods:
> - Its name;
> - Its signature including signaled exceptions;
> - Its behavior in terms of pre-conditions and post-conditions.
>
> Note that the creators are missing. Omitting creators allows subtypes to =
provide different creators than their supertypes. In addition, omitting cre=
ators makes it easy for a type to have multiple implementations, allows new=
 creators to be added later, and re ects common usage: for example, Java in=
terfaces and virtual types provide no way for users to create objects of th=
e type.

(Creators is Liskov's word for constructors, which they later define
in the paper.)

Hope that helps.

- Mark Trapp

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



Re: [PHP-DEV] [RFC][Discussion] Why can constructors violate LSP?

2023-11-24 Thread Mark Trapp
On Thu, Nov 23, 2023 at 12:31 PM Robert Landers
 wrote:
>
> Hello Internals,
>
> As you may know, an inherited method cannot reduce the visibility of
> an overridden method. For example, this results in a fatal error
> during compilation:
>
> class P {
> public function hello($name = 'world') {
> echo "hello $name\n";
> }
> }
>
> class C extends P {
> private function hello($name = 'world') {
> parent::hello($name);
> echo "goodbye $name\n";
> }
> }
>
> However, we can make certain methods private anyway, namely,
> constructors (I haven't gone hunting for other built-in methods yet).
> This is perfectly allowed:
>
> class P {
> public function __construct($name = 'waldo') {
> echo "hello $name\n";
> }
> }
>
> class C extends P {
> private function __construct($name = 'world') {
> parent::__construct($name);
> echo "goodbye $name\n";
> }
> }
>
> To my somewhat trained eye, this appears to violate the Liskov
> Substitution Principle, for example, this now can have hidden errors:
>
> function create(P $class) {
> return new (get_class($class))();
> }
>
> proven by:
>
> $c = (new ReflectionClass(C::class))
>   ->newInstanceWithoutConstructor();
>
> create($c);
>
> Even though we thought we knew that the constructor was declared public.
>
> I'd like to propose an RFC to enforce the covariance of constructors
> (just like is done for other methods), to take effect in PHP 9, with a
> deprecation notice in 8.3.x.
>
> I'm more than happy to implement it.
>
> Does anyone feel strongly about this one way or the other?
>
> Robert Landers
> Software Engineer
> Utrecht NL

Hi Robert,

Liskov and Wing explicitly exempted constructors in their original
paper describing the principle:
http://reports-archive.adm.cs.cmu.edu/anon/1999/CMU-CS-99-156.pdf

> 4.1 Type Specifications
>
> A type specification includes the following information:
>
> - The type's name
> - A description of the type's value space
> - A definition of the type's invariant and history properties
> - For each of the type's methods:
> - Its name;
> - Its signature including signaled exceptions;
> - Its behavior in terms of pre-conditions and post-conditions.
>
> Note that the creators are missing. Omitting creators allows subtypes to 
> provide different creators than their supertypes. In addition, omitting 
> creators makes it easy for a type to have multiple implementations, allows 
> new creators to be added later, and re ects common usage: for example, Java 
> interfaces and virtual types provide no way for users to create objects of 
> the type.

(Creators is Liskov's word for constructors, which they later define
in the paper.)

Hope that helps.

- Mark Trapp

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



Re: [PHP-DEV] [RFC][Discussion] Why can constructors violate LSP?

2023-11-24 Thread Robert Landers
On Fri, Nov 24, 2023 at 5:06 AM Mike Schinkel  wrote:
>
>
>
> On Nov 23, 2023, at 4:50 PM, Robert Landers  wrote:
>
> On Thu, Nov 23, 2023 at 10:30 PM Deleu  wrote:
>
>
> Constructors are an implementation detail of a specialized class and as such 
> they're not subject to LSP because the goal of LSP is to be able to make sure 
> that any object of a given type hierarchy can be used to accomplish a certain 
> behavior. If you take a step back from PHP's dynamic nature and think about 
> LSP from a more pure type system, the fact you're expecting an object of type 
> C, but then you completely disregard everything about the object itself and 
> dive into it's metadata to build another object, that's the moment you're no 
> longer playing by the rules of OOP. It's like those mathematical equations 
> that prove that 1 = 2, they all have one thing in common: they end up 
> dividing by 0 at some point.
>
> OOP here dictates that you should reach for patterns like Builder, Abstract 
> Factory or similar. That way you constraint yourself to the rules of OOP and 
> you won't get weird outcomes.
>
> From another point of view, when a type is expected by a function or method, 
> all we can expect from it is whatever was defined as the blueprint 
> (class/interface) of that object and the __construct() is a special method 
> that is not assumed to be part of that blueprint because it's not reasonable 
> to do `$object->__construct();` after receiving an object. As such, a 
> constructor cannot break LSP because the constructor is not part of the 
> object's API from a "receptor" point of view.
>
> I don't have a vote so take my opinion with a bucket of salt, but if I could 
> I would definitely vote against such RFC.
>
>
> --
> Marco Deleu
>
>
> Thanks Marco,
>
> That's an interesting perspective and one I would agree with for the
> most part, especially if you take my illustration at face value. Where
> it gets weird/breaks down is when you have a class-string, that you
> assert is the correct type, and then try to instantiate it:
>
> // from somewhere
> $class = "C";
>
> if(is_subclass_of($class, P::class)) {
>$example = new $class("world");
> }
>
> If PHP didn't offer these built-in methods, then I would fully agree
> with you, but it does, which puts it into a weird position where
> sometimes a class is substitutable, and in this one special case, it
> is not.
>
>
> What Marco said pretty much mirrors the answer in Software Engineering SE:
>
> https://softwareengineering.stackexchange.com/a/270738/9114
>
> As if probably obvious now, when you are using a variable containing a class 
> name to instantiate a class you are actually not using OOP, you are using the 
> text expansion capabilities of PHP. The fact that it appears to be similar to 
> a method call is just likely coincidence,
>
> Now if classes were first class objects in PHP, then there might be an 
> argument, but alas, they are not.
>
> Anyway, I always addressed the problem you are running into by defining a 
> `make_new()` static method.  Here's what your example might look after an 
> initial refactor:
>
> class P {
> public $msg;
> function __construct($msg){
> $this->msg = $msg;
> }
> }
> class C extends P {
> static function make_new($props){
> return new C($props["msg"]);
> }
> }
>
> $class = "C";
>
> if(is_subclass_of($class, P::class)) {
>$example = $class::make_new( ["msg"=>"world"] );
>print_r($example);
> }
>
> Of course, getting it to production code will take a lot more, like you can 
> see here (although this code has now been update for probably five years 
> since I am no longer coding in PHP professionally):
>
> https://github.com/search?q=repo%3Awplib%2Fwplib%20make_new=code
>
> And if you are dealing with 3rd party classes, you'll need to write wrapper 
> classes.
>
> Hope this helps.
>
> -Mike
>

Hey Mike,

Using a static method to enforce LSP when it should have been enforced
in the first place, merely proves my point that it violates LSP. I
don't know how else to spell it out, I guess we can try this one next:

class A {
final public static function foo() {
return new static();
}
}

class B extends A {
public function __construct(private string $name) {}
}

$a = B::foo();

It just plainly violates LSP and you can't write safe code that
touches constructors. I don't know how else to spell this out.

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



Re: [PHP-DEV] [RFC][Discussion] Why can constructors violate LSP?

2023-11-23 Thread Mike Schinkel


> On Nov 23, 2023, at 4:50 PM, Robert Landers  wrote:
> 
> On Thu, Nov 23, 2023 at 10:30 PM Deleu  wrote:
>> 
>> Constructors are an implementation detail of a specialized class and as such 
>> they're not subject to LSP because the goal of LSP is to be able to make 
>> sure that any object of a given type hierarchy can be used to accomplish a 
>> certain behavior. If you take a step back from PHP's dynamic nature and 
>> think about LSP from a more pure type system, the fact you're expecting an 
>> object of type C, but then you completely disregard everything about the 
>> object itself and dive into it's metadata to build another object, that's 
>> the moment you're no longer playing by the rules of OOP. It's like those 
>> mathematical equations that prove that 1 = 2, they all have one thing in 
>> common: they end up dividing by 0 at some point.
>> 
>> OOP here dictates that you should reach for patterns like Builder, Abstract 
>> Factory or similar. That way you constraint yourself to the rules of OOP and 
>> you won't get weird outcomes.
>> 
>> From another point of view, when a type is expected by a function or method, 
>> all we can expect from it is whatever was defined as the blueprint 
>> (class/interface) of that object and the __construct() is a special method 
>> that is not assumed to be part of that blueprint because it's not reasonable 
>> to do `$object->__construct();` after receiving an object. As such, a 
>> constructor cannot break LSP because the constructor is not part of the 
>> object's API from a "receptor" point of view.
>> 
>> I don't have a vote so take my opinion with a bucket of salt, but if I could 
>> I would definitely vote against such RFC.
>> 
>> 
>> --
>> Marco Deleu
> 
> Thanks Marco,
> 
> That's an interesting perspective and one I would agree with for the
> most part, especially if you take my illustration at face value. Where
> it gets weird/breaks down is when you have a class-string, that you
> assert is the correct type, and then try to instantiate it:
> 
> // from somewhere
> $class = "C";
> 
> if(is_subclass_of($class, P::class)) {
>$example = new $class("world");
> }
> 
> If PHP didn't offer these built-in methods, then I would fully agree
> with you, but it does, which puts it into a weird position where
> sometimes a class is substitutable, and in this one special case, it
> is not.

What Marco said pretty much mirrors the answer in Software Engineering SE:

https://softwareengineering.stackexchange.com/a/270738/9114 


As if probably obvious now, when you are using a variable containing a class 
name to instantiate a class you are actually not using OOP, you are using the 
text expansion capabilities of PHP. The fact that it appears to be similar to a 
method call is just likely coincidence,

Now if classes were first class objects in PHP, then there might be an 
argument, but alas, they are not.

Anyway, I always addressed the problem you are running into by defining a 
`make_new()` static method.  Here's what your example might look after an 
initial refactor:

class P {
public $msg;
function __construct($msg){
$this->msg = $msg;
}
}
class C extends P {
static function make_new($props){
return new C($props["msg"]);
}
}

$class = "C";

if(is_subclass_of($class, P::class)) {
   $example = $class::make_new( ["msg"=>"world"] );
   print_r($example);
}

Of course, getting it to production code will take a lot more, like you can see 
here (although this code has now been update for probably five years since I am 
no longer coding in PHP professionally):

https://github.com/search?q=repo%3Awplib%2Fwplib%20make_new=code 


And if you are dealing with 3rd party classes, you'll need to write wrapper 
classes.

Hope this helps.

-Mike



Re: [PHP-DEV] [RFC][Discussion] Why can constructors violate LSP?

2023-11-23 Thread Robert Landers
On Thu, Nov 23, 2023 at 10:30 PM Deleu  wrote:
>
>
>
> On Thu, Nov 23, 2023 at 5:31 PM Robert Landers  
> wrote:
>>
>> Hello Internals,
>>
>> As you may know, an inherited method cannot reduce the visibility of
>> an overridden method. For example, this results in a fatal error
>> during compilation:
>>
>> class P {
>> public function hello($name = 'world') {
>> echo "hello $name\n";
>> }
>> }
>>
>> class C extends P {
>> private function hello($name = 'world') {
>> parent::hello($name);
>> echo "goodbye $name\n";
>> }
>> }
>>
>> However, we can make certain methods private anyway, namely,
>> constructors (I haven't gone hunting for other built-in methods yet).
>> This is perfectly allowed:
>>
>> class P {
>> public function __construct($name = 'waldo') {
>> echo "hello $name\n";
>> }
>> }
>>
>> class C extends P {
>> private function __construct($name = 'world') {
>> parent::__construct($name);
>> echo "goodbye $name\n";
>> }
>> }
>>
>> To my somewhat trained eye, this appears to violate the Liskov
>> Substitution Principle, for example, this now can have hidden errors:
>>
>> function create(P $class) {
>> return new (get_class($class))();
>> }
>>
>> proven by:
>>
>> $c = (new ReflectionClass(C::class))
>>   ->newInstanceWithoutConstructor();
>>
>> create($c);
>>
>> Even though we thought we knew that the constructor was declared public.
>>
>> I'd like to propose an RFC to enforce the covariance of constructors
>> (just like is done for other methods), to take effect in PHP 9, with a
>> deprecation notice in 8.3.x.
>>
>> I'm more than happy to implement it.
>>
>> Does anyone feel strongly about this one way or the other?
>
>
> Constructors are an implementation detail of a specialized class and as such 
> they're not subject to LSP because the goal of LSP is to be able to make sure 
> that any object of a given type hierarchy can be used to accomplish a certain 
> behavior. If you take a step back from PHP's dynamic nature and think about 
> LSP from a more pure type system, the fact you're expecting an object of type 
> C, but then you completely disregard everything about the object itself and 
> dive into it's metadata to build another object, that's the moment you're no 
> longer playing by the rules of OOP. It's like those mathematical equations 
> that prove that 1 = 2, they all have one thing in common: they end up 
> dividing by 0 at some point.
>
> OOP here dictates that you should reach for patterns like Builder, Abstract 
> Factory or similar. That way you constraint yourself to the rules of OOP and 
> you won't get weird outcomes.
>
> From another point of view, when a type is expected by a function or method, 
> all we can expect from it is whatever was defined as the blueprint 
> (class/interface) of that object and the __construct() is a special method 
> that is not assumed to be part of that blueprint because it's not reasonable 
> to do `$object->__construct();` after receiving an object. As such, a 
> constructor cannot break LSP because the constructor is not part of the 
> object's API from a "receptor" point of view.
>
> I don't have a vote so take my opinion with a bucket of salt, but if I could 
> I would definitely vote against such RFC.
>
>
> --
> Marco Deleu

Thanks Marco,

That's an interesting perspective and one I would agree with for the
most part, especially if you take my illustration at face value. Where
it gets weird/breaks down is when you have a class-string, that you
assert is the correct type, and then try to instantiate it:

// from somewhere
$class = "C";

if(is_subclass_of($class, P::class)) {
$example = new $class("world");
}

If PHP didn't offer these built-in methods, then I would fully agree
with you, but it does, which puts it into a weird position where
sometimes a class is substitutable, and in this one special case, it
is not.

Robert Landers
Software Engineer
Utrecht NL

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



Re: [PHP-DEV] [RFC][Discussion] Why can constructors violate LSP?

2023-11-23 Thread Deleu
On Thu, Nov 23, 2023 at 5:31 PM Robert Landers 
wrote:

> Hello Internals,
>
> As you may know, an inherited method cannot reduce the visibility of
> an overridden method. For example, this results in a fatal error
> during compilation:
>
> class P {
> public function hello($name = 'world') {
> echo "hello $name\n";
> }
> }
>
> class C extends P {
> private function hello($name = 'world') {
> parent::hello($name);
> echo "goodbye $name\n";
> }
> }
>
> However, we can make certain methods private anyway, namely,
> constructors (I haven't gone hunting for other built-in methods yet).
> This is perfectly allowed:
>
> class P {
> public function __construct($name = 'waldo') {
> echo "hello $name\n";
> }
> }
>
> class C extends P {
> private function __construct($name = 'world') {
> parent::__construct($name);
> echo "goodbye $name\n";
> }
> }
>
> To my somewhat trained eye, this appears to violate the Liskov
> Substitution Principle, for example, this now can have hidden errors:
>
> function create(P $class) {
> return new (get_class($class))();
> }
>
> proven by:
>
> $c = (new ReflectionClass(C::class))
>   ->newInstanceWithoutConstructor();
>
> create($c);
>
> Even though we thought we knew that the constructor was declared public.
>
> I'd like to propose an RFC to enforce the covariance of constructors
> (just like is done for other methods), to take effect in PHP 9, with a
> deprecation notice in 8.3.x.
>
> I'm more than happy to implement it.
>
> Does anyone feel strongly about this one way or the other?
>

Constructors are an implementation detail of a specialized class and as
such they're not subject to LSP because the goal of LSP is to be able to
make sure that any object of a given type hierarchy can be used to
accomplish a certain behavior. If you take a step back from PHP's
dynamic nature and think about LSP from a more pure type system, the fact
you're expecting an object of type C, but then you completely disregard
everything about the object itself and dive into it's metadata to build
another object, that's the moment you're no longer playing by the rules of
OOP. It's like those mathematical equations that prove that 1 = 2, they all
have one thing in common: they end up dividing by 0 at some point.

OOP here dictates that you should reach for patterns like Builder, Abstract
Factory or similar. That way you constraint yourself to the rules of OOP
and you won't get weird outcomes.

>From another point of view, when a type is expected by a function or
method, all we can expect from it is whatever was defined as the blueprint
(class/interface) of that object and the __construct() is a special method
that is not assumed to be part of that blueprint because it's not
reasonable to do `$object->__construct();` after receiving an object. As
such, a constructor cannot break LSP because the constructor is not part of
the object's API from a "receptor" point of view.

I don't have a vote so take my opinion with a bucket of salt, but if I
could I would definitely vote against such RFC.


-- 
Marco Deleu


Re: [PHP-DEV] [RFC][Discussion] Why can constructors violate LSP?

2023-11-23 Thread Rowan Tommins
On 23 November 2023 20:31:09 GMT, Robert Landers  
wrote:

>I'd like to propose an RFC to enforce the covariance of constructors
>(just like is done for other methods), to take effect in PHP 9, with a
>deprecation notice in 8.3.x.

There's a lot more than visibility that is enforced on normal methods, but 
isn't on constructors. For instance this is also valid:

class A {
public function __construct(int $foo) {}
}
class B extends A {
public function __construct(string $bar) {}
}

From a theoretical perspective, I think the argument is roughly that classes 
aren't first-class citizens that you can pass around, so substitutability 
doesn't apply. You can't for instance write a function that explicitly depends 
on "a class definition inheriting from A", like this:

function foo(class $class) {
 $instance = new $class(42);
}

You can certainly simulate such code with some strings and maybe a bit of 
reflection, but the language isn't going to make any guarantees about it.

I did just think of a counter-example, though, which is that "new 
static($param)" is allowed, even though there's no way to know if $param will 
be accepted by subclasses. Maybe it shouldn't be allowed?


From a practical point of view, it's often very useful to sub-class something 
and provide a constructor with a different signature. Maybe your subclass has 
additional dependencies; maybe it can hard-code or calculate some of the inputs 
to the parent constructor for a special case, etc.

A private constructor can be used in conjunction with static methods to 
simulate multiple named constructors (createFromString, createFromRequest, 
etc). Given the lack of other guarantees, there's no particular gain in 
preventing that just because the parent class has a public constructor.

Regards,

-- 
Rowan Tommins
[IMSoP]

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



[PHP-DEV] [RFC][Discussion] Why can constructors violate LSP?

2023-11-23 Thread Robert Landers
Hello Internals,

As you may know, an inherited method cannot reduce the visibility of
an overridden method. For example, this results in a fatal error
during compilation:

class P {
public function hello($name = 'world') {
echo "hello $name\n";
}
}

class C extends P {
private function hello($name = 'world') {
parent::hello($name);
echo "goodbye $name\n";
}
}

However, we can make certain methods private anyway, namely,
constructors (I haven't gone hunting for other built-in methods yet).
This is perfectly allowed:

class P {
public function __construct($name = 'waldo') {
echo "hello $name\n";
}
}

class C extends P {
private function __construct($name = 'world') {
parent::__construct($name);
echo "goodbye $name\n";
}
}

To my somewhat trained eye, this appears to violate the Liskov
Substitution Principle, for example, this now can have hidden errors:

function create(P $class) {
return new (get_class($class))();
}

proven by:

$c = (new ReflectionClass(C::class))
  ->newInstanceWithoutConstructor();

create($c);

Even though we thought we knew that the constructor was declared public.

I'd like to propose an RFC to enforce the covariance of constructors
(just like is done for other methods), to take effect in PHP 9, with a
deprecation notice in 8.3.x.

I'm more than happy to implement it.

Does anyone feel strongly about this one way or the other?

Robert Landers
Software Engineer
Utrecht NL

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