Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:111869 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 56110 invoked from network); 16 Sep 2020 04:22:09 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 16 Sep 2020 04:22:09 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 88E97180531 for ; Tue, 15 Sep 2020 20:30:24 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,HTML_MESSAGE,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2, SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.2 X-Spam-Virus: No X-Envelope-From: Received: from mail-qt1-f172.google.com (mail-qt1-f172.google.com [209.85.160.172]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Tue, 15 Sep 2020 20:30:23 -0700 (PDT) Received: by mail-qt1-f172.google.com with SMTP id p65so5115134qtd.2 for ; Tue, 15 Sep 2020 20:30:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=newclarity-net.20150623.gappssmtp.com; s=20150623; h=from:message-id:mime-version:subject:date:in-reply-to:cc:to :references; bh=0DBV9/C3XxRL2odI8cuI10b/JsukIN96M9Jl0FFwnaE=; b=NQ7JfkV65H4zdEKrcNWlmyT/KoJTm9GeqIRCEj3syFzCvpTfH0yFExVUoFFNzktQp1 SDoPBewtml1iCEOkIze3QDdB4VsiCUqeGJelHtOGROF1XhIJJFt8DgHjhyQFtBwpHDxw +qUt2rYvjX/L0dYTu2i3BYwMlAe0TUIFOvr3XwXOsBHswSVWB9VTDhbK/fTX11v1dABM /BMAAMIUhbehC3bWPIlG25S7FK0OuBsDe2tvER07wPT2iYAeoL9kPPHzqLBYv0r/7fT7 jdomVUIsQAITZAnvIl1WItpWkMmIxx/lxsWcxpecM6IJTT0B3NpAj+a2ekKCKg+qfZ1N tTRQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:message-id:mime-version:subject:date :in-reply-to:cc:to:references; bh=0DBV9/C3XxRL2odI8cuI10b/JsukIN96M9Jl0FFwnaE=; b=g+HtEKw29tKYVKzeG0Pgo6ycbFsE0MEdHnx3fzzm4muAmDIxwsVuIEdfHFBgOyTWDi mpyajHxHUbJYIA2ZOr1Wec9Fj3XFOUAN55pcSYlIiyQzOiwVY9GRsWE25CYePbtZ2/2e 9ZWTE8QrQUYbX6nT8OqJcIfbcYd4SQbRmaq1f+esCWIzRujlpwmYfCKVDTrgSyGrFuBK qm5m1JoNwK5jI9NlCQxaRH5et5sE5iRsJNBB/li3456+Vtgw6Vwij4M+qqaQqCupWAn/ HJB2fe0L/AlCNiZCbe8n8KuoA8tbHSciXuOzMC14AMQ06+B1jrqPis48vz57q+/cwF31 Hz9A== X-Gm-Message-State: AOAM530f1MpVbJYdg108K2ZZ8BBD3CEXlqfKsLBkaBZty8/s5Wsd1keu Hma8mSILGS/cqAYAcbofLkQCaQ== X-Google-Smtp-Source: ABdhPJz75V3PjfgEsTlJ35FwLxQ32QB7DA8/lBO8oSY8sbLN3bikow10aSfG26vb9LjBRE6uTFVlUg== X-Received: by 2002:ac8:4e4e:: with SMTP id e14mr9017308qtw.49.1600227020659; Tue, 15 Sep 2020 20:30:20 -0700 (PDT) Received: from ?IPv6:2601:c0:c680:5cc0:cce9:c0f6:49df:f4be? ([2601:c0:c680:5cc0:cce9:c0f6:49df:f4be]) by smtp.gmail.com with ESMTPSA id c13sm17529288qtq.5.2020.09.15.20.30.17 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Tue, 15 Sep 2020 20:30:19 -0700 (PDT) Message-ID: <0D61C423-4973-4F62-A448-C6F2DD04AB90@newclarity.net> Content-Type: multipart/alternative; boundary="Apple-Mail=_D5CF04C9-4E28-4140-AE81-C9803D71C459" Mime-Version: 1.0 (Mac OS X Mail 13.4 \(3608.120.23.2.1\)) Date: Tue, 15 Sep 2020 23:30:18 -0400 In-Reply-To: Cc: Benjamin Eberlei , PHP Internals To: =?utf-8?Q?Olle_H=C3=A4rstedt?= References: X-Mailer: Apple Mail (2.3608.120.23.2.1) Subject: Re: [PHP-DEV] Add interface implementation to class in separate file From: mike@newclarity.net (Mike Schinkel) --Apple-Mail=_D5CF04C9-4E28-4140-AE81-C9803D71C459 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 > On Sep 13, 2020, at 3:49 PM, Olle H=C3=A4rstedt = wrote: >=20 > 2020-09-13 17:58 GMT, Benjamin Eberlei >: >> On Sat, Sep 12, 2020 at 10:23 PM Olle H=C3=A4rstedt = >> wrote: >>=20 >>> Hi internals! >>>=20 >>> Separation of data and behaviour is both a fun and hard discussion, >>> especially considering: >>>=20 >>> * "It should be possible to add new features without touching old = code"; >>> and >>> * "Principle of Least Privilege" (never expose more than you have = to) >>> (https://en.wikipedia.org/wiki/Principle_of_least_privilege). >>>=20 >>> There should (could) be a way to add new behaviour to old data = without >>> touching the old data (class). Traits won't work in this use-case, >>> since they assume the same internal structure for all trait-using >>> classes. Imagine the `stringable` interface and a `toString` trait. = A >>> __toString() method needs knowledge about the internal structure of = a >>> class Foo. Yet if we want to keep adding behaviour to Foo, we'll end >>> up with either exposing too much of Foo, or expanding the class file >>> indefinitely. Please note that composition is not a proper solution, >>> since it requires exposure of Foo; composition leads to lack of = proper >>> encapsulation, or representation exposure. >>>=20 >>> In Haskell it's possible to split instance implementation of >>> type-classes into separate files. In Rust you can have a struct with >>> private fields and put impl of behaviour in different files (but = same >>> crate). >>>=20 >>> A similar feature in PHP could look like (using new keyword `expand` >>> but could be anything, or even `extend` in new context): >>>=20 >>> ``` >>> // File FooStringable.php >>> expand Foo implements stringable { >>> public function __toString() { >>> // Full access to Foo's all private fields here. >>> // Assumes you can autoload Foo. >>> // Assumes usage of $foo->__toString(); will be configured with >>> autoload to dynamically find the correct behaviour of Foo. >>> } >>> } >>> ``` >>>=20 >>> If you'd use composition instead, you'd maybe have a formatter class >>> with a method `$formatter->toString(stringable $foo)`. This has the >>> problem I mentioned with exposing too much of $foo; it breaks >>> encapsulation. It has the benefit of being able to provide multiple >>> toString methods with different formats, but would have to assume >>> similar structure of the objects passed to it (defined with an >>> interface), which is not always possible or desirable. >>>=20 >>> The other way is inheritance, which doesn't scale over multiple >>> behaviours. `FooWithStringable extends Foo`? No. >>>=20 >>> Was I clear here? Do you understand the issues that this design >>> pattern is trying to solve? Its purpose is to solve "keep adding new >>> feature to old data" in a clean and proper way, while keeping >>> information encapsulation. >>>=20 >>=20 >> Do I understand you correctly, it would be somewhat like "opening" up = a >> class and making changes to it in another file? >>=20 >> Certainly a powerful concept, but I would be very interested in the = details >> how that would interact with autoloading. If I have a class Foo = loaded, and >> its "extension" FooString with toString method not, then it would = lead to >> the "toString" code missing. >=20 > Yes, a little like opening up, *but* with clear restrictions. It was > explained to me that this won't work without either: >=20 > 1) A module system to define which files are part of a class > 2) Manually write in the "main" class file which other extensions to > this class should be loaded. >=20 > The reason is again encapsulation - it should not be possible for any > file to just get access to private fields by adding a new interface > implementation. >=20 > Option (2) can be achieved if we allow "include ;" in PHP > *inside* a class definition. Again note that this is different from a > trait, since it gives access to private properties that are > *different* between the classes using it, like toString() or > toQuery(). >=20 > Option (2) does not need configured autoloading. Option (1) is more > elaborate, maybe composer.json would need to configure something, *or* > it is assumed that a class is defined in a single folder instead of a > single file. Which already is kind of like a module system. Doesn't option 2 fail the very first criteria you argued for in your = initial email? Doesn't it fail the criteria "It should be possible to = add new features without touching old code?"=20 IOW, we should not have to modify any source code in Symfony to be able = to expand one of its classes, for example. If modifying the original = source is required, you really don't need a new syntax, just modify the = class and add your toString method. (BTW, your first criteria sounds like it would be enabling the = Open/closed principle of S.O.L.I.D. for PHP classes.) Also, I expect requiring a module system would eliminate this idea from = even being considered given past discussions of "modules." So, back to what Benjamin implied, for this to even be considered there = would need to be an elegant and performant way to handle the autoloading = of such class expansions. The challenge is =E2=80=94 without annotating = the class itself =E2=80=94 how do you know when an expansion exists = without first explicitly naming it or looping through the autoloader = potentially many times per class? -Mike= --Apple-Mail=_D5CF04C9-4E28-4140-AE81-C9803D71C459--