Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:111852 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 9570 invoked from network); 13 Sep 2020 20:42:02 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 13 Sep 2020 20:42:02 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id EE7741804AA for ; Sun, 13 Sep 2020 12:49:42 -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-f66.google.com (mail-ej1-f66.google.com [209.85.218.66]) (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 ; Sun, 13 Sep 2020 12:49:42 -0700 (PDT) Received: by mail-ej1-f66.google.com with SMTP id p9so20247781ejf.6 for ; Sun, 13 Sep 2020 12:49:42 -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=Fw07m6fRfsRAFfqxTr1UnBMCv5ZICwltk9tGijU1aHI=; b=pV0uWpHvrSoBTwuOhgqNAhBhbMi5yOS0wFSd5QW0b7DwFdfyj15cHtKkBypi6sUQNk SAJIzdXih55SJCVG3MiF0ys9fOaU24setY7MoKrcKX0jUs/Eo59Qczvf+EJQr047JsNq 1e8zuwuO6SLb+E5hGTfQ3vOHpHW9/Rj4Pk1qZsyyIhXi7WJC15f9dUezrbBk8DwmAqlP NdPomGVpo80XEqqq74QL7PQdBVCowIMyOrsJb8j39aR/lLYtf9q3cxygaHNnYjNvqrrT 1rwxA0d05j393s32wauoJld9mOVuOUVL9gC1afN28Y7bkncnfMlYEAqceDdRO1VLJw96 6jxg== 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=Fw07m6fRfsRAFfqxTr1UnBMCv5ZICwltk9tGijU1aHI=; b=Z/0Z0799w62D8a3VAuVjFeC40UNXdzFHg+tvvUunYPm9zF90EnWDZWE4w3ZA844tuY YJuPRy2VYF1wTaj81IHm5jL6w/6wfO6+MNmGhCvAsysnnLBjefV0ussW/Gv4EJaqCIfx l1miVEaSPU4TcK3XrUSF7X5v9Hl0Q9SaWeFt5lo+NiqhGkxA4n7jgsL1s51pMoJ/ApFH dlE0GmNZKpJkYJyay0xnwoCP4CV2e2zGdINGIO/+ipzvsK6ENZ2btxXf1TTymKSf4uIS 1arlqLkuM8lApUc/s81a78lYUF1UxVjN6EeATBOWsx43ypEx09aESvs1CQ6nEG2MzAzE Du3A== X-Gm-Message-State: AOAM53321dFW8vihFGjzTnrMkdNXocbBedxLdiMCzfjBZeLAbq0vpv6c ZKrAxXV9q2CCrWIu0qHJlnzkOw3yMaC3DZvrBVajzjmL X-Google-Smtp-Source: ABdhPJxkxzATWdRSkb4waArU4RLsrgdJpbGM/gVFw0LvOTTHlyLNv739fkDbGwwLJyD01r0zdl+QW0gQzTWxTrrZo5A= X-Received: by 2002:a17:906:e103:: with SMTP id gj3mr11249159ejb.153.1600026577872; Sun, 13 Sep 2020 12:49:37 -0700 (PDT) MIME-Version: 1.0 Received: by 2002:a54:3ac6:0:0:0:0:0 with HTTP; Sun, 13 Sep 2020 12:49:37 -0700 (PDT) In-Reply-To: References: Date: Sun, 13 Sep 2020 19:49:37 +0000 Message-ID: To: Benjamin Eberlei Cc: 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-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 detai= ls > how that would interact with autoloading. If I have a class Foo loaded, a= nd > 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. In Rust, it's possible to spread out behaviour (impl) to a certain data (struct) in the same _crate_ while keeping access to private fields: https://users.rust-lang.org/t/implement-private-struct-in-different= -files/29407 C# has something called "partial class": https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-an= d-structs/partial-classes-and-methods Olle