Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:111873 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 16689 invoked from network); 16 Sep 2020 20:20:37 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 16 Sep 2020 20:20:37 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 947C41804C3 for ; Wed, 16 Sep 2020 12:29:04 -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=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM, RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE, SPF_PASS autolearn=no autolearn_force=no version=3.4.2 X-Spam-Virus: No X-Envelope-From: Received: from mail-ej1-f52.google.com (mail-ej1-f52.google.com [209.85.218.52]) (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 ; Wed, 16 Sep 2020 12:29:04 -0700 (PDT) Received: by mail-ej1-f52.google.com with SMTP id i22so12372676eja.5 for ; Wed, 16 Sep 2020 12:29:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :cc:content-transfer-encoding; bh=O+/zLa+wDp3MLFbOFBfbx1hhcVCGOlw+Cr4d9/DvAUU=; b=bbpaYVlCzC6x2tF/NrDoIJm1euUQ76dV524l2Ymp0iaYgQ5XBhWw/VB3hb/ksN53S5 W9VGr2yz7VYZIr+J7BgJkSGcYIjLwNui6cy8BdM8ElAdCVcfiT1Kpakby29oCQOJkaYp 6MiACdDb7v8xYGV8pyFDpdvL4T2sFZRjdszDIjs3UbIhwCNpneU6zWNU4kIne4Tpkx7N 6MfAsZZqd8yIrqitGwZh3m2sv/ckwYuH24vllUlZOkwiO0hPNy380LSEt1Hser6tpJqQ ScMKd53M5e9CefcxNeAsHPMgAWW1R4PZZ7o8vJn+8+WzUIFUeS+DfqQBl+7vgSDuC5cQ 4rLQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:in-reply-to:references:from:date :message-id:subject:to:cc:content-transfer-encoding; bh=O+/zLa+wDp3MLFbOFBfbx1hhcVCGOlw+Cr4d9/DvAUU=; b=YJ9mTtKWsdCOmux26KgqTbL8beKWajddYiLEW0Ia5e4AupOjd5jb7ZttE4CjdMrrT4 Wl0u/1sxKtb7ZAlcFScJxym0iGS6suSTBdK1t+a5cEttVM/E2hjCJBeFBKtVv67ophd0 SiRMJX4IvcA4edh3xM4zRr3otY/hsFgPL7qNF+swwcA1AtT5mnTEun6WHSeYg0aSH2FV YK/0JNcsHHKFegzVVpidSSpPPj0AZ8inRdEgZKV4SxuGUwOmYcoRTu0srpGnbqf++vgu BAWHDGOg7iJu5mIwbTjd27Mpdk2fbq7bDvfbNEqjEkE1rYbRGw/FqfGKGvPyaOUKcWr1 hQSw== X-Gm-Message-State: AOAM532jUpWneJkqU/WEtM3jMdcb+UH2IhTEam1KYngz8Q7/+ytRViVw WKo141o9eM/gTK1gAD1+TBiXN4bEntdfSTAW61w= X-Google-Smtp-Source: ABdhPJyePpdBiQB8vZodVw1O16ffixoLi55t6zjAWYFRA5VLUzk+Hl5EBcpCg0LN7qoCh6Lmx9FXDPStZKWmtEWF7sA= X-Received: by 2002:a17:906:914b:: with SMTP id y11mr28110810ejw.145.1600284540473; Wed, 16 Sep 2020 12:29:00 -0700 (PDT) MIME-Version: 1.0 Received: by 2002:a54:3ac6:0:0:0:0:0 with HTTP; Wed, 16 Sep 2020 12:28:59 -0700 (PDT) In-Reply-To: <0D61C423-4973-4F62-A448-C6F2DD04AB90@newclarity.net> References: <0D61C423-4973-4F62-A448-C6F2DD04AB90@newclarity.net> Date: Wed, 16 Sep 2020 19:28:59 +0000 Message-ID: To: Mike Schinkel Cc: Benjamin Eberlei , PHP Internals Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Subject: Re: [PHP-DEV] Add interface implementation to class in separate file From: olleharstedt@gmail.com (=?UTF-8?Q?Olle_H=C3=A4rstedt?=) 2020-09-16 3:30 GMT, Mike Schinkel : > > >> On Sep 13, 2020, at 3:49 PM, Olle H=C3=A4rstedt >> wrote: >> >> 2020-09-13 17:58 GMT, Benjamin Eberlei > >: >>> On Sat, Sep 12, 2020 at 10:23 PM Olle H=C3=A4rstedt >>> wrote: >>> >>>> Hi internals! >>>> >>>> Separation of data and behaviour is both a fun and hard discussion, >>>> especially considering: >>>> >>>> * "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). >>>> >>>> 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. >>>> >>>> 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). >>>> >>>> A similar feature in PHP could look like (using new keyword `expand` >>>> but could be anything, or even `extend` in new context): >>>> >>>> ``` >>>> // 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. >>>> } >>>> } >>>> ``` >>>> >>>> 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. >>>> >>>> The other way is inheritance, which doesn't scale over multiple >>>> behaviours. `FooWithStringable extends Foo`? No. >>>> >>>> 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. >>>> >>> >>> Do I understand you correctly, it would be somewhat like "opening" up a >>> class and making changes to it in another file? >>> >>> 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. >> >> Yes, a little like opening up, *but* with clear restrictions. It was >> explained to me that this won't work without either: >> >> 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. >> >> 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. >> >> 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(). >> >> 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 init= ial > email? Doesn't it fail the criteria "It should be possible to add new > features without touching old code?" Yeah, kind of. Just trying to find the shortest path to make it work in PHP. At least it would be possible to split classes in this case, and also split data from behaviour if you want, while keeping full encapsulation. > > IOW, we should not have to modify any source code in Symfony to be able t= o > 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 a= dd > your toString method. > > (BTW, your first criteria sounds like it would be enabling the Open/close= d > principle of S.O.L.I.D. for PHP classes.) > > Also, I expect requiring a module system would eliminate this idea from e= ven > being considered given past discussions of "modules." I had a thought of namespace-private class properties, but I'll start another thread for that. Swift has access level "internal" for this purpose. > 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 e= xplicitly > naming it or looping through the autoloader potentially many times per > class? Yeah, good points. I don't have an answer right now. > > -Mike Someone told me about extensions in Swift language, which is a good example of what I mean: https://docs.swift.org/swift-book/LanguageGuide/Extensions.html Olle