Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:125740 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by qa.php.net (Postfix) with ESMTPS id B5FB11A00BD for ; Thu, 3 Oct 2024 13:32:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1727962498; bh=6D2NHe81xBBLDB0JTh8VaDIg/3pz0ZRd9FPObVtBHw4=; h=From:Date:Subject:To:From; b=Qx9idMpdA315m0sCV5geb8F1hcUOM7pz1q6OIUADLkNIdCH0SvGNuO4VUGZM3MsQG yFu2fReOTVrQYIRVtqZzcVuH8xuA7CzBWNyuARti2u+oAH7pSv7CUJt71GYZwqrw41 +b0wAaZIN8jjdZeB9w0J6x6lXGVOqw+638S6ujKfZ3ub8uhq6kwoWc20vwrsPPKSh2 d6DYMniIwDPgVpJEEG/HuNUyz+u5nzAxAthCP/YL18gOuO/U95kbV/6AEG0dzQ12Ua ti3V1mdfHkis2Fwob3/GHEpB0IhZDqENsr0ByGkTx1qM1X6akK8aff1jMoa8UWIUTH 4QZ6GSdjumPjQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 9254D180061 for ; Thu, 3 Oct 2024 13:34:57 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=0.6 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from mail-wm1-f46.google.com (mail-wm1-f46.google.com [209.85.128.46]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Thu, 3 Oct 2024 13:34:54 +0000 (UTC) Received: by mail-wm1-f46.google.com with SMTP id 5b1f17b1804b1-42cbface8d6so12527525e9.3 for ; Thu, 03 Oct 2024 06:32:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1727962359; x=1728567159; darn=lists.php.net; h=to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; bh=JVpB/anz1KRvmrBOFaUlkTgT6kd0btqbRJvaein2A7s=; b=PRynAmVPWPyy7ScrPjqHHBFbkNJOebdxNEQ9JdvBF2IQ32+FMba9i6MOpNVWYS4xMs O/TOvb1mg41J4ygVUP0UfuxylywDgiHfEGGzc2Et19T8FLbp8OJfEznQHeEIOeGNpb80 U/eGA0LLN0fF3BnZE+5M+taEU1UD4QeUPr13JBooklnq/gmrHbKpX6Pkc+NVontcOkbd e2i5hQozOuqAgJH9Xvx8nyWSJGJ7bYZrHMwvadXInUfNK/wSH9vGv5au6i8rcKgXeMEZ qtmpKU6Q3IN6/X0UVMJ+0dstcKmDFQbb2yu+1uH0IkmOJ4bHxhO0lhZgk6NtnKY7nxpo pgsg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727962359; x=1728567159; h=to:subject:message-id:date:from:mime-version:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=JVpB/anz1KRvmrBOFaUlkTgT6kd0btqbRJvaein2A7s=; b=CHuFAbwOzvh0CPjCh7iSbic5/XGtiZZQdMK/DPlnNOODZBRfkgPgUZu5tL0otErq1x JtKRO+RbKMSAARDxRaYMerNCA1iV1s/eA8fy+bBxYZaUBhfn8OqnVkGmJ9sfZ75AYQE3 v42ktMEGHz/uMUNuYShcdfdGeS2lUIvZYdFbeP88j6zhZaOw9Wf2G5DxOTeEnIqWee5q j5oICRjin4KAUMFx3pByfY8EiWWLVueAqPRF5m10MbTcDoT6KXJJKkYSdFskuNb+PGSm bmA3ZwWndDCZKRkxgX60fRYX8PrjyA12bDFsiZ7b7DNHrXoDT3F3OIfs2cIwkgvWOMs0 3R7g== X-Gm-Message-State: AOJu0YwZP2I19ds5+WlmaKf52E28cN2kflYslTnd0f6HNdwF2oSaIdvy FAIQHJSV5hZUqpu0TnHwxtX7rqvU/uVA/w61JIPdUZ315mhgvBpPg4C5QMvP6INZj1zhBz68DM0 kcYZNd3Dw4B3PXPQf2WongFY3TYSKL7WmCw== X-Google-Smtp-Source: AGHT+IFmtsyOB1xEdbODpbX1IaqGeRKkjawxjppihLa4AIk/gjPToihDxIdwnhC9TxCUlTJYEuENadaAHWeLy3njZmU= X-Received: by 2002:a05:600c:35cf:b0:42c:b555:43dd with SMTP id 5b1f17b1804b1-42f777b2039mr66280685e9.3.1727962358513; Thu, 03 Oct 2024 06:32:38 -0700 (PDT) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 Date: Thu, 3 Oct 2024 16:32:27 +0300 Message-ID: Subject: [PHP-DEV] Asymmetric visibility Reflection API problems To: php internals Content-Type: text/plain; charset="UTF-8" From: udaltsov.valentin@gmail.com (Valentin Udaltsov) Hi, internals! Recently I've started testing the new features of the PHP 8.4 Reflection API and found some of them unintuitive. At first, I reported this as a bug [1], but then Ilija explained that the behavior is intentional. So I've decided to discuss the issue publicly. We are going to talk about the asymmetric visibility RFC [2] and the new `ReflectionProperty::is(Private|Protected)Set()` methods. Consider a class and reflected facts about it's properties [3]: ```php class A { // isPrivateSet() = true (OK) public private(set) mixed $public_private_set; // isPrivateSet() = false (I would expect true) private private(set) mixed $private_private_set; // isPrivateSet() = false (I would expect true) private mixed $private; // isProtectedSet() = true (OK, explained in the RFC) public readonly mixed $public_readonly; // isProtectedSet() = false (I would expect true) protected readonly mixed $protected_readonly; // isProtectedSet() = false (I would expect true) protected protected(set) readonly mixed $protected_protected_set_readonly; // isPrivateSet() = false (OK), isProtectedSet() = false (OK) public bool $virtual_no_set_hook { get => true; } } ``` Here's why it works this way. Internally properties with symmetric visibility do not have an asymmetric flag. That is why `private private(set)` and `private` both have `isPrivateSet() = false`. Readonly properties without explicit `set` imply `protected(set)` [4] and thus they are symmetric when protected and asymmetric otherwise. I personally don't agree that Reflection API should stick to the internal implementation details. On the contrary, it should hide them and make it easier to study the facts about the code. Alright. Let's keep that in mind and jump into another issue. I've suddenly realised, that the new Reflection API breaks backward compatibility. In PHP <8.4 you can check `$reflectionProperty->isPublic() && !$reflectionProperty->isReadonly()` and then safely write to that property from global scope. Now this is not the case anymore: if a public property has a `private set()`, a simple Hydrator working on these assumptions will break in 8.4 and require refactoring. The reason for this is that before 8.4 `ReflectionProperty::isPublic() = true` guaranteed that the property has public get and set, now it only guarantees that property has a public get. Here's what I propose: - `ReflectionProperty::isPublic()` returns `true` only if the property is symmetrically public. For `public readonly $prop` it will return `false`, because it's implicitly `public protected(set) readonly $prop`. This is a BC break, but a smaller one, given that libraries now take "readonly" into account. - `ReflectionProperty::isProtected()` returns `true` only if the property is symmetrically protected. For `protected readonly $prop` it will return ``true``, because it's implicitly `protected protected(set) readonly $prop`. Fully backward compatible. - `ReflectionProperty::isPrivate()` returns `true` only if the property is symmetrically private. Fully backward compatible. - Add `ReflectionProperty::isPublicGet()`: returns `true` if property is `public` or `public XXX(set)`. - Add `ReflectionProperty::isProtectedGet()`: returns `true` if property is `protected` or `protected XXX(set)`. - Add `ReflectionProperty::isPrivateGet()`: returns `true` if property is `private` or `private private(set)`. - Add `ReflectionProperty::isPublicSet()`: returns `true` if property is symmetrically public (asymmetric public set is not possible). - Change `ReflectionProperty::isProtectedSet()`: returns `true` if property is symmetrically protected or has an asymmetric protected set. - Change `ReflectionProperty::isPrivate()`: returns `true` if property is symmetrically private or has an asymmetric private set. ```php class A { // isPublic() = false, isPrivate() = false, isPublicGet() = true, isPrivateSet() = true public private(set) mixed $public_private_set; // isPrivate() = true, isPrivateGet() = true, isPrivateSet() = true private private(set) mixed $private_private_set; // isPrivate() = true, isPrivateGet() = true, isPrivateSet() = true private mixed $private; // isPublic() = false, isProtected() = false, isPublicGet() = true, isProtectedSet() = true public readonly mixed $public_readonly; // isProtected() = true, isProtectedGet() = true, isProtectedSet() = true protected readonly mixed $protected_readonly; // isProtected() = true, isProtectedGet() = true, isProtectedSet() = true protected protected(set) readonly mixed $protected_protected_set_readonly; // isPublic() = false, isPublicGet() = true, isPublicSet() = false, isProtectedSet() = false, isPrivateSet() = false public bool $virtual_no_set_hook { get => true; } } ``` The most difficult question is whether we can have these or similar changes in 8.4 after RC1... Does a BC break in the current implementation (if we agree that it exists) give us such an option? Also, Ilija has a brilliant idea to add `isReadable($scope): bool`, `isWritable($scope): bool` methods [5], but this can be done in PHP 9. 1. https://github.com/php/php-src/issues/16175 2. https://wiki.php.net/rfc/asymmetric-visibility-v2 3. https://3v4l.org/O3FNN/rfc#vgit.master 4. https://wiki.php.net/rfc/asymmetric-visibility-v2#relationship_with_readonly 5. https://github.com/php/php-src/issues/16175#issuecomment-2389966021 -- Best regards, Valentin