Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:109223 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 59017 invoked from network); 23 Mar 2020 07:43:27 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 23 Mar 2020 07:43:27 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 8F49F1804E0 for ; Sun, 22 Mar 2020 23:07:30 -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,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL, SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=3.4.2 X-Spam-ASN: AS15169 209.85.128.0/17 X-Spam-Virus: No X-Envelope-From: Received: from mail-qt1-f179.google.com (mail-qt1-f179.google.com [209.85.160.179]) (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, 22 Mar 2020 23:07:29 -0700 (PDT) Received: by mail-qt1-f179.google.com with SMTP id t9so6793653qto.9 for ; Sun, 22 Mar 2020 23:07:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=newclarity-net.20150623.gappssmtp.com; s=20150623; h=mime-version:subject:from:in-reply-to:date:cc :content-transfer-encoding:message-id:references:to; bh=jvNDbz1kpWhUlRHOI0OiTTJA1yc5r0hJkbVJpPNsA9A=; b=IXsHb3AFKXNJcmwNlaBEtMEWEUnkXz9Ng/S5D4QGc4Q0H/RrV8mtsBhDcjgdvRw23X z6rUN6Gf7ZBhBGB/tWVPHw6n0ajc0TiZEmY9iqy/NloxqUDGhG1dNWtIpblYpy1EVAk7 1LZ91LAmaSgBfHgYairnnayJNvkExFzb2cKgfM52Hb0COJ6NRIxos6MNspMxoBcFH8qX dGn5+nT7G9hpPEG/ITGR1sbWzWaxY4m0gNG9pOpTl+eytcw/IB75a2iZxzCdPgVT/EBu d0n7JIBok0FalyRAmUJrDMe4RVa/Ti1KTpv357iIRsYSIDSNHLb3g1cU8z1T0HcSBRd+ Lv+w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:subject:from:in-reply-to:date:cc :content-transfer-encoding:message-id:references:to; bh=jvNDbz1kpWhUlRHOI0OiTTJA1yc5r0hJkbVJpPNsA9A=; b=GH7HFcGChfQx1RRff5jXTHjS5kKdKhMeAD9651OHuVXvvLN0yI47LeZodKd7i83HFR FFyegXu182FEor3RTQniLQMoXDQRqxd7W7YfMT5zOsc7Rdx0Es4YhqP8xImU3anx+Hl+ Spa06IlV0ZGwD4gZx8c5mhIvisY0+/KXk4fAcmiSvwrh60Er/ywAb52m65XT+j+D+033 9XVh/LAuoVVG2u1W3P8kwnEI0R++uuiqvewuP/hA8+RPSGqztwHiQCFcKCHiR0IW7JTx tCIT1FXrBYJmscNeg8QfaNZb+9TMdwIWWbqniSrXjpu/QET50FdKetwEbOqoucau9Mww y3Xw== X-Gm-Message-State: ANhLgQ32CzkSPSVfnBBEW/niAKtQ4wzaKkLJQk1FD5d0yH9EvFiY0amM I5gGvbtq9Hdj+UmY+es7GtKnSvJBPB9Mfw== X-Google-Smtp-Source: ADFU+vsXoFRJYWEXqrq53TGVPvZQcRzen9YOe6D05tmevtK4sBbW8pg0i0xk2YEG6XxBy+pLBCjpAQ== X-Received: by 2002:aed:3225:: with SMTP id y34mr19743341qtd.19.1584943647019; Sun, 22 Mar 2020 23:07:27 -0700 (PDT) Received: from ?IPv6:2601:c0:c680:5cc0:b161:bce7:21ab:aa25? ([2601:c0:c680:5cc0:b161:bce7:21ab:aa25]) by smtp.gmail.com with ESMTPSA id 69sm10298227qki.131.2020.03.22.23.07.26 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 22 Mar 2020 23:07:26 -0700 (PDT) Content-Type: text/plain; charset=utf-8 Mime-Version: 1.0 (Mac OS X Mail 12.4 \(3445.104.11\)) In-Reply-To: <1b781e1e-3f27-485b-ab47-5eeaf9496548@www.fastmail.com> Date: Mon, 23 Mar 2020 02:07:25 -0400 Cc: php internals Content-Transfer-Encoding: quoted-printable Message-ID: References: <1b781e1e-3f27-485b-ab47-5eeaf9496548@www.fastmail.com> To: Larry Garfield X-Mailer: Apple Mail (2.3445.104.11) Subject: Re: [PHP-DEV] Improving PHP's Object Egonomics: A broad analysis From: mike@newclarity.net (Mike Schinkel) > On Mar 22, 2020, at 8:47 PM, Larry Garfield = wrote: > https://hive.blog/php/@crell/improving-php-s-object-ergonomics Hi Larry, That is a really excellent writeup. Thanks from me at least for taking = the time to write it up in depth. Looking at your conclusion, my gut feelings tell me your conclusion is = about 85% there, but with about 15% that gives me pause unless = addressed, which I think is imminently possible.=20 I'm going to do my best to explain what I am envisioning but realize I = had not reserved time for this today so I am rushing to complete before = my Monday starts in too few hours. Given that I will almost certainly = not be as clear in my thoughts had I had time to write and review it so = please ask for clarification if something does not make sense.=20 --- 1. Your post mentions validation needed for value objects =E2=80=94 a = concern I share =E2=80=94 but AFAICT your conclusion does not address = the issue. 2. It also mentions overlapping concerns and references numerous RFCs, = but there were two other relative works not mentioned. One[1] is an RFC = and the other[2] a PR on Github. I think they both overlaps with this = problem space, with the former addressing validation whereas the latter = case potentially conflicts with constructor promotion. 3. And finally, some of the proposed concepts add syntax elements but do = not make them first class language elements. ---=20 But before I cover those let me cover what I think you nailed to = perfection: 1. The need resolve the verbosity of day-to-day programming with classes = on PHP. 2. The distinction between Service and Value objects. 3. The value of immutability. 4. The downside of using arrays to pass properties (which is how many of = us do it today.) 5. The benefits of contextual access 6. The limited nature of COPA/and write-once properties. 7. The elegance of property accessor syntax and how it can be applied to = create readonly properties 8. The elegance of named parameters with the distinction between BC = parameter passing and a newer JSON object-like syntax. 9. Rust-like cloning/Construct from syntax. 10. The problem of exposing parameter names as part of an external API = for existing code 11. The problems associated with get and set properties actually running = code when not expected by the user. ---=20 However, here is were I think your proposal still needs tightening up. 1. You don't really address the value object vs. service object = distinction, except on the periphery. 2. You mention the concerns about exposing parameter names as part of = the API but don't address those concerns directly. 3. You mention get/set properties surprisingly running code but do not = address those concerns directly. 4. Your concept for a JSON object-like syntax for passing parameter = feels incomplete to me. Unless I miss understand it is just a special = syntax that only works in the context of passing arguments to a = constructor, method or function, and not a first-class language element. = If we were to go that route I think we would find it highly limiting. 5. In the section you say that "either of the following construction = styles becomes possible" but you do not talk about the option of having = one of more of the first parameters being positional and the rest being = able to be passed by name, which I think would be an ideal use-case when = you want to force certain parameters to always be passed but make the = rest optional. --- 1. Let me now address the conflict with Nikita's "decorator" pattern = support PR (which I and several others think would be better called = delegation.) (As an aside, Go has such a feature and after having used = it I find working with PHP's lack of delegation to be extremely painful. = Well at least we have Traits. You can see my comment on that PR and my = proposal here[3] and [4].) I think that delegation addresses the no-win scenario of and thus is an = extremely important addition not to block: - "Should I extend a class and have fragile base classes?"=20 - "Should I use containment and then have a nightmare of boilerplate?", = or - "Should I use magic methods and loose performance and native ability = use reflection and *_exists() functions?" Unfortunately I think that Constructor Promotion assumes that all = properties will be associated with the class at hand, and not with = classes that are being delegated to. Yes we could assume they are joined = together, and distributed to the various delegated instances, but I = believe that could get very complicated very quickly. 2. Going further with constructor promotion, which you join with named = parameters, how are the parameters represented inside the = constructor/method/function? As different variables methods just like = current parameters? Is there no concept of automatic aggregation of = those parameters into some kind of structure that could then be passed = down to other methods and functions? 3. And =E2=80=94 I don't want to bike-shed =E2=80=94 but I think the = proposed constructor promotion syntax could result in potentially high = levels of visual complexity that all must be syntactically correct = across many different lines. That has the making of some very fragile = code and code that is rather hard to follow, much like when developers = write "metadata driven" code where they instantiate an array that = contains many other arrays and the entire structure traverses several = hundred lines of code or more. ---- One key language addition solves many of the above problems. Imagine that we add a "structs" to PHP. As I envision it: A struct is a value object that has properties but either no methods or = limited methods. Structs don't need to be backward compatibility except not to conflict = with existing syntax. Structs are as simple as this (public is assumed for properties in my = examples, unless otherwise specified): struct Person { string $firstName string $lastName } Structs don't have constructors but do have an initializer syntax so = Structs would be created like this: $person =3D Person{firstName: 'Mike', lastName: 'Schinkel' } This of a struct as a lightweight object with a few non-BC rules (like = pass-by-value, more on that below).=20 Struct properties could be accessed just like object properties, with = the thin arrow ("->"): echo $person->lastName; Structs could potentially have get/set so as to allow read-only or write = only properties: struct Person { // This is read only private string $firstName { public get(); private set(); } // This is write only private string $lastName { private get(); public set(); } } Structs could possibly also support "Materialized Values" assuming those = accessors were limited to only accessing other properties (but that = might be really hard to implement): struct Person { public string $firstName; public string $lastName; private string $fullName { public get(){ return sprint('%s %s', $this->firstName, $this->lastName ); } } } Structs could also be extended via classes. class Developer extends Person { function work() { // writes code } }=20 Even better classes could delegate to Structs: class Developer { use struct Person; }=20 And we could use aliases=20 class Developer { use struct Person as person; }=20 And we could disambiguate class DevelopmentManager { use class Manager; use class Developer { work as writeCode; } }=20 Further, we could support define named parameters by defining a Struct. = Combine that with Construct-from syntax and it rid of the multi-line = complexity and mess of the mess of Constructor Promotion: namespace Structs; struct Person { public string $firstName; public string $lastName; } namespace BizObjects; class Person{ use struct Structs\Person; } Better, we could allow sharing of names between a struct and a class (if = in the same file) which could associate them: struct Person { public string $firstName; public string $lastName; } class Person{ use struct; } Which could be equivalent to: class Person{ use struct { public string $firstName; public string $lastName; } } Given the above the following function would only accept the object = Person: function renderPersonCard( Person $person ) { /// rendering... } This however could access the struct person: function renderPersonCard( Person::struct $person ) { /// rendering... } With the above class/struct Person, you might create like this: $person =3D new Person(Person{firstName: 'Mike', lastName: 'Schinkel' = }); Or the shorthand: $person =3D new Person({firstName: 'Mike', lastName: 'Schinkel' }); The above needs no explicit constructor, but what if we need to provide = one? Note this example automatically initializes firstName and = lastName: class Person{ private static $_people =3D array(); use struct { public string $firstName; public string $lastName; } function __construct(Person::struct) { self::$_people[] =3D $this; } } Then there is the concern about parameters that are always required. = They could be handled like this implicitly: class Person{ private static $_people =3D array(); use struct { public int $personId; public string $firstName; public string $lastName; } function __construct(int $personId, Person::struct); } Or explicitly: class Person{ use struct { public int $personId; public string $firstName; public string $lastName; } function __construct(int $personId, Person::struct) { $this->personId =3D $personId; } } We could even move $personId out of the struct and into the class, if we = wanted to: class Person{ public int $personId; use struct { public string $firstName; public string $lastName; } function __construct(int $personId, Person::struct) { $this->personId =3D $personId; } } But what about our development manager? Note that a class would have an = automatic Struct which would just be its properties: class DevelopmentManager { use class Manager; use class Developer { work as writeCode; } function __construct(Manager::struct, Developer::struct) { self::$_people[] =3D $this; } }=20 The above would require instantiation like this: $person =3D new DevelopmentManager( Manager{firstName: 'Mike', lastName: 'Schinkel' },=20 Developer{ide:"PhpStorm", languages:["PHP","Go","SQL","etc.]} ); Which could be shortened to: $person =3D new DevelopmentManager( {firstName: 'Mike', lastName: 'Schinkel' }, {ide:"PhpStorm", languages:["PHP","Go","SQL","etc.]} ); Or with this construct: class DevelopmentManager { use class Manager; use class Developer { work as writeCode; } function __construct(Manager::struct + Developer::struct) { self::$_people[] =3D $this; } }=20 Could be shortened further to: $person =3D new DevelopmentManager({ firstName: 'Mike',=20 lastName: 'Schinkel',=20 ide:"PhpStorm",=20 languages:["PHP","Go","SQL","etc."], }); Structs could also support immutability by having them passed to methods = and functions by value =E2=80=94 like arrays =E2=80=94 instead of by = reference like objects. In addition, if we embrace annotations we can address validation (note = I'm using 'attribute(s)' keyword because <<>> created too much visual = noise and annotation is longer IMO): class Person{ use struct { attributes NotEmpty, ProperCase public string $firstName; attributes NotEmpty, ProperCase public string $lastName; attribute ValidEmail public string $email; attributes CanBeEmpty, ValidPhone public string $phone; } } --- Notice how the inclusion if Structs solve so many of the edge cases of = the original proposal, and provide significant new capabilities in a = very elegant manner? It addresses: 1. Value objects and possibly Materialized values 2. Need for backward compatibility with objects but not with structs 3. Unifying value objects with classes 4. By-value passing supports immutability 5. Delegation is not blocked 6. The "Named parameters" syntax is directly supported because it = becomes the syntax to create struct instances. 7. It leverages Construct-from syntax 8. It reduces repeated references without forcing their declarations = between contractor/method/function parentheses 9. Supports working with attributes/annotations better than Constructor = promotion 10. And it is fully compatible with Get/Set property accessors. So there you have it. I probably missed something and I probably was not = clear enough in some area. Please do me the favor and if you have a = question about how something will work first try to envision how it = could work so you can suggest that when replying. Looking forward to your thoughts.=20 -Mike P.S. Unfortunately with all the demand lately they seem to have run out = of flame-retardant suits so I present this with no such protection. --- [1] https://wiki.php.net/rfc/annotations_v2 [2] https://github.com/php/php-src/pull/5168 [3] https://github.com/php/php-src/pull/5168#issuecomment-586688715 [4] https://mikeschinkel.me/2020/adding-delegation-to-php/#summary