diff --git a/drafts/short-class.md b/drafts/short-class.md new file mode 100644 index 0000000..3624d2c --- /dev/null +++ b/drafts/short-class.md @@ -0,0 +1,562 @@ +# PHP RFC: Nested Classes + +* Version: 0.5 +* Date: 2025-02-08 +* Author: Rob Landers, rob@bottled.codes +* Status: Under Discussion (Accepted or Declined) +* First Published at: + +## Introduction + +This RFC proposes a significant enhancement to the PHP language: **Nested Classes**. +Nested classes allow the definition of one class within another, +enabling tighter encapsulation and better organization of related logic. +This feature naturally supports common design patterns such as Builders, DTOs, and serialization helpers, +reducing boilerplate, minimizing namespace clutter, and clarifying code structure. +Unlike some other languages, PHP nested classes do not implicitly capture the outer class instance, +preserving explicit data flow and minimizing hidden coupling. + +## Proposal + +Nested classes allow defining classes (including enums) within other classes, +interfaces, or enums, +following standard visibility rules. +This allows developers to declare a class as `private` or `protected` and restrict its usage to an outer class. + +```php +class Outer { + public class Inner { + public function __construct(public string $message) {} + } + + private class PrivateInner { + public function __construct(public string $message) {} + } +} + +$foo = new Outer\Inner('Hello, world!'); +echo $foo->message; +// outputs: Hello, world! +$baz = new Outer\PrivateInner('Hello, world!'); +// Uncaught TypeError: Cannot instantiate class Outer\PrivateInner from the global scope +``` + +Nested classes are just regular class definitions with visibility. +That means, +except where otherwise noted, "how would nested classes work in situation X?" +can be answered with "the same as any other class/object." + +### Use-cases and examples + +Nested classes are a powerful tool for organizing code and encapsulating functionality. Some use-cases include: + +- **Builder patterns**: Nested classes can be used to create complex objects step-by-step, encapsulating the construction logic within the outer class. +- **Data Transfer Objects (DTOs)**: Nested classes can be used to define data structures that are closely related to the outer class, improving code organization and readability. +- **Serialization helpers**: Nested classes can be used to define custom serialization logic for the outer class, encapsulating the serialization details within the outer class. +- **Encapsulation**: Nested classes can be used to encapsulate functionality that is only relevant to the outer class, reducing the visibility of the nested class and improving code organization. +- **Namespace management**: Nested classes can help reduce the number of top-level classes in a namespace, improving code organization and reducing clutter. + +#### Example: Serialization DTOs + +```php +class Message { + public function __construct(private string $message, private string $from, private string $to) {} + + public function Serialize(): string { + return serialize(new SerializedMessage($this)); + } + + private class SerializedMessage { + public string $message; + public string $from; + public string $to; + + public function __construct(Message $message) { + $this->message = $message->message; + $this->from = $message->from; + $this->to = $message->to; + } + } +} +``` + +#### Example: Builder Pattern + +```php +class Person { + public class Builder { + use AgeCalculator; + + private string $name; + private DateTimeInterface $age; + + public function setName(string $name): self { + $this->name = $name; + return $this; + } + + public function setBirthday(DateTimeInterface $age): self { + $this->age = $age; + return $this; + } + + public function build(): Person { + return new Person($this->name, $this->calculateAge($this->age)); + } + } + + public function __construct(private string $name, private int $age) {} +} +``` + +#### Example: DTOs + +```php +enum Status { + case Active; + case Inactive; + + private enum Processing { + case Pending; + case Completed; + } + + public class Message { + + private Processing $processing = Processing::Pending; + + public function __construct(private Status $status) {} + + public function getMessage(): string { + return match ([$this->status, $this->processing]) { + [Status::Active, Processing::Pending] => "Active and Pending", + [Status::Active, Processing::Completed] => "Active and Completed", + [Status::Inactive, Processing::Pending] => "Inactive and Pending", + [Status::Inactive, Processing::Completed] => throw new LogicException('should not happen'), + }; + } + } + + public function createMessage(): Message { + return new Message($this); + } +} +``` + +#### Example: class shared via interface + +```php +interface Reader { + public class Buffer { + public string $bytes; + } + + public function read(): void; + public function getBuffer(): Buffer; +} + +// in another file: +use Reader\Buffer; + +class FileReader implements Reader { + protected Buffer $buffer; + + public function __construct() { + $this->buffer = new Buffer(); + } + + public function read(): void { + $this->buffer->bytes = random_bytes(5); + } + + public function getBuffer(): Buffer { + return $this->buffer; + } +} +``` + +### Why PHP needs nested classes + +PHP developers today often simulate nested classes through long namespace chains or anonymous classes +embedded in methods. +While these patterns work, they come with visibility challenges, +verbosity, and a lack of cohesion between related types. +Nested classes solve these problems natively, +offering better encapsulation without requiring new runtime behavior or complex inheritance tricks. + +### Declaration Syntax + +Declaring a nested class uses the same syntax as declaring a top-level class. +You can define `readonly`, `final`, and `abstract` nested classes. +In addition, nested classes may be declared with visibility modifiers (`private`, `protected`, `public`), +which determine where the nested class can be referenced or instantiated. + +If a nested class does not explicitly declare visibility (private, protected, or public), +it is implicitly considered public. +This matches PHP’s existing class behavior for methods and properties. + +```php +class Outer { + private class Inner {} + protected readonly class ReadOnlyInner {} + public abstract class AbstractInner {} + public final class FinalInner {} +} +``` + +Outer classes remain "public" (accessible and visible to all), and visibility modifiers cannot be applied to them. + +### Visibility + +Visibility refers to whether a method, class, or property is accessible from outside its defining context. +A nested class may be declared as `private`, `protected`, or `public`. +A private class will _only_ be visible within the lexical outer class, +while a protected class is visible both inside its lexical scope and any child classes. +A public class has no restrictions and will be visible from anywhere in the codebase. + +#### Properties and Methods + +All outer class’s properties and methods are visible from nested classes, +as from the perspective of the nested class, it is a member of the outer class. +However, unlike some languages with nested classes, +an outer class does not have direct access to a nested class’s private or protected members. + +#### Method return type and argument declarations + +Nested classes may only be used as a return type or argument declarations for methods and functions +that have the same visibility or less. +Thus returning a `protected` type from a `public` method is not allowed, +but is allowed from a `protected` or `private` method. + +| Inner Class Visibility | Method Visibility | Allowed | +|------------------------|-------------------|---------| +| `public` | `public` | Yes | +| `public` | `protected` | Yes | +| `public` | `private` | Yes | +| `protected` | `public` | No | +| `protected` | `protected` | Yes | +| `protected` | `private` | Yes | +| `private` | `public` | No | +| `private` | `protected` | No | +| `private` | `private` | Yes | + +Attempting to declare a return of a non-visible type will result in a `TypeError`: + +```php +class Outer { + private class Inner {} + + public function getInner(): Inner { + return new Inner(); + } +} + +// Fatal error: Uncaught TypeError: Public method getInner cannot return private class Outer\Inner +new Outer()->getInner(); +``` + +This does not prevent the programmer from returning a hidden type, only from declaring it. +The programmer may choose to use an interface or public type instead: + +```php +interface FooBar {} + +class Outer { + private class Inner implements FooBar {} + + public function getInner(): FooBar { + return new Inner(); + } +} +``` + +#### Properties + +The visibility of a type declaration on a property must also not increase the visibility of a nested class. +Thus, a public property cannot declare a private type. + +```php +class Outer { + private class Inner implements FooBar {} + + public function getInner(): FooBar { + return new Inner(); // not an error + } +} +``` + +### Accessing outer classes + +Nested classes do not implicitly have access to an outer class instance. +This design choice avoids implicit coupling between classes and keeps nested classes simpler, +facilitating potential future extensions +(such as adding an explicit outer keyword) without backward compatibility issues. + +However, a programmer may pass an instance of an outer class (or another nested class) to a nested class. +If a class has access to the outer class, +it may use the outer class’s private, protected, and public properties and methods. + +```php +class Message { + public function __construct(private string $message, private string $from, private string $to) {} + + public function Serialize(): string { + return new SerializedMessage($this)->message; + } + + private class SerializedMessage { + public string $message; + + public function __construct(Message $message) { + $this->message = strlen($message->from) . $message->from . strlen($message->to) . $message->to . strlen($message->message) . $message->message; + } + } +} +``` + +### Resolution + +From a resolution perspective, outer classes behave as a pseudo-namespace. +Using a short name of a nested class will resolve to the nested class. +To disambiguate between different classes with the same name in the namespace and the nested class, +the programmer can use fully qualified names. + +```php +class Inner {} + +abstract class Outer { + class Inner {} + public function foo(Inner $i); // resolves to Outer\Inner + public function bar(\Inner $i); // resolves to global Inner +} +``` + +This applies to deeply nested classes as well: + +```php +class Outer { + class Middle { + class Inner {} + } + public function foo(Middle\Inner $i); // resolves to Outer\Middle\Inner +} +``` + +This can lead to accidentally shadowing a class in the same namespace, +thus requiring the use of `use` statements or fully qualified names to disambiguate. + +### Reflection + +Several new methods are added to `ReflectionClass` to help support inspection of nested classes: + +```php +$reflection = new ReflectionClass('\a\namespace\Outer\Inner'); + +$reflection->isNestedClass(); // true +$reflection->isPublic() || $reflection->isProtected() || $reflection->isPrivate(); // true +$reflection->getName(); // \a\namespace\Outer\Inner +$reflection->getShortName(); // Inner +``` + +For non-nested classes, `ReflectionClass::isNestedClass()` returns `false`. + +A future addition to `ReflectionClass::getNestedClasses()` may allow enumeration of nested class declarations. + +### Autoloading + +Since nested classes are referenced using standard \-separated names, +the engine treats them like any other class name during resolution. +If the outer class has not yet been loaded, PHP will attempt to autoload the nested class name, +which may result in fallback behavior depending on the autoloader. +Implementers are encouraged to structure autoloaders to load the outer class when a nested class is referenced. + +### Usage + +Nested classes may be defined in the body of any class-like structure, except traits: + +- in a class body +- in an anonymous class body +- in an enum body +- in an interface body (public nested classes only) + +Traits are not allowed to contain nested classes and are left to a future RFC. +Traits are excluded from this RFC +because nested class resolution via use statements introduces ambiguities about scope and visibility. +These concerns are deferred to a future RFC. + +### Outer class effects + +Nested classes are fully independent of their outer class’s declaration modifiers. +Outer class modifiers such as abstract, final, +or readonly do not implicitly cascade down or affect the nested classes defined within them. + +Specifically: + +- An abstract outer class can define concrete (non-abstract) nested classes. + Nested classes remain instantiable, + independent of the outer class’s abstractness. +- A final outer class does not force its nested classes to be final. + Nested classes within a final class can be extended + internally, providing flexibility within encapsulation boundaries. +- A readonly outer class can define mutable nested classes. + This supports internal flexibility, allowing the outer class + to maintain immutability in its external API while managing state changes internally via nested classes. + +Examples: + +```php +abstract class Service { + // Valid: Nested class is not abstract, despite the outer class being abstract. + public class Implementation { + public function run(): void {} + } +} + +// Allowed; abstract outer class does not force nested classes to be abstract. +new Service\Implementation(); +``` + +```php +readonly class ImmutableCollection { + private array $items; + + public function __construct(array $items) { + $this->items = $items; + } + + public function getMutableBuilder(): Builder { + return new Builder($this->items); + } + + public class Builder { + private array $items; + + public function __construct(array $items) { + $this->items = $items; + } + + public function add(mixed $item): self { + $this->items[] = $item; + return $this; + } + + public function build(): ImmutableCollection { + return new ImmutableCollection($this->items); + } + } +} +``` + +In this example, even though `Builder` is a mutable nested class within a readonly outer class, +the outer class maintains its immutability externally, +while nested classes help internally with state management or transitional operations. + +#### Interfaces + +Nested classes (including enums) may be defined in an interface body; however, they must be public classes. +This is because an interface always defines public methods and properties, so this is to maintain consistency. + +### Abstract nested classes + +It is worth exploring what an `abstract` nested class means and how it works. +Abstract nested classes are allowed to be the parent of any class that can see them. +For example, a private abstract class may only be inherited by subclasses in the same outer class. +A protected abstract class may be inherited by a nested class in a subclass of the outer class or a nested class in the +same outer class. +However, this is not required by subclasses of the outer class. + +Abstract nested classes may not be instantiated, just as abstract outer classes may not be instantiated. + +```php +class OuterParent { + protected abstract class InnerBase {} +} + +// Middle is allowed, does not have to implement InnerBase +class Middle extends OuterParent {} + +// Last demonstrates extending the abstract inner class explicitly. +class Last extends OuterParent { + private abstract class InnerExtended extends OuterParent\InnerBase {} +} + +``` + +## Backward Incompatible Changes + +- This RFC introduces new syntax and behavior to PHP, which does not conflict with existing syntax. +- Some error messages will be updated to reflect nested classes, and tests that depend on these error messages are likely + to fail. +- Tooling using AST or tokenization may need to be updated to support the new syntax. + +Note: Applying visibility modifiers to class declarations is new syntax, but only valid in nested contexts. +Top-level classes will continue to behave as before, and this change introduces no ambiguity in existing code. + +## Proposed PHP Version(s) + +This RFC targets the next version of PHP. + +## RFC Impact + +### To SAPIs + +None. + +### To Existing Extensions + +None were discovered during testing, but it is possible there are unbundled extensions that may be affected. + +### To Opcache + +This change introduces a new opcode, AST, and other changes that affect opcache. +These changes are included as part of the PR that implements this feature. + +### To Tooling + +Tooling that uses AST or tokenization may need to be updated to support the new syntax, +such as syntax highlighting, static analysis, and code generation tools. + +## Open Issues + +Pending discussion. + +## Unaffected PHP Functionality + +There should be no change to any existing PHP syntax. + +## Future Scope + +- Inner interfaces +- Inner traits + +This RFC focuses on class and enum nesting. +Interfaces and traits are excluded for now due to their unique visibility and resolution semantics, +but may be considered in follow-up RFCs. + +## Proposed Voting Choices + +As this is a significant change to the language, a 2/3 majority is required. + + + + * Yes + * No + + + +## Patches and Tests + +To be completed. + +## Implementation + + + +## References + +- [email that inspired this RFC](https://externals.io/message/125975#125977) +- [RFC: Records](https://wiki.php.net/rfc/records) + +## Rejected Features + +TBD diff --git a/drafts/template.md b/drafts/template.md index 6142836..eedddc1 100644 --- a/drafts/template.md +++ b/drafts/template.md @@ -1,9 +1,10 @@ # PHP RFC: Your Title Here -\* Version: 0.9 \* Date: 2013-02-24 (use today's date here) \* Author: -Your Name, your_email_address@example.com \* Status: Draft (or Under -Discussion or Accepted or Declined) \* First Published at: - +* Version: 0.9 +* Date: 2013-02-24 (use today's date here) +* Author: Your Name, your_email_address@example.com +* Status: Draft (or Under Discussion or Accepted or Declined) +* First Published at: This is a suggested template for PHP Request for Comments (RFCs). Change this template to suit your RFC. Not all RFCs need to be tightly diff --git a/published/short-class.ptxt b/published/short-class.ptxt new file mode 100644 index 0000000..860f7e9 --- /dev/null +++ b/published/short-class.ptxt @@ -0,0 +1,480 @@ +====== PHP RFC: Nested Classes ====== + + * Version: 0.5 + * Date: 2025-02-08 + * Author: Rob Landers, + * Status: Under Discussion (Accepted or Declined) + * First Published at: http://wiki.php.net/rfc/short-and-inner-classes + +===== Introduction ===== + +This RFC proposes a significant enhancement to the PHP language: **Nested Classes**. Nested classes allow the definition of one class within another, enabling tighter encapsulation and better organization of related logic. This feature naturally supports common design patterns such as Builders, DTOs, and serialization helpers, reducing boilerplate, minimizing namespace clutter, and clarifying code structure. Unlike some other languages, PHP nested classes do not implicitly capture the outer class instance, preserving explicit data flow and minimizing hidden coupling. + +===== Proposal ===== + +Nested classes allow defining classes (including enums) within other classes, interfaces, or enums, following standard visibility rules. This allows developers to declare a class as ''%%private%%'' or ''%%protected%%'' and restrict its usage to an outer class. + + +class Outer { + public class Inner { + public function __construct(public string $message) {} + } + + private class PrivateInner { + public function __construct(public string $message) {} + } +} + +$foo = new Outer\Inner('Hello, world!'); +echo $foo->message; +// outputs: Hello, world! +$baz = new Outer\PrivateInner('Hello, world!'); +// Uncaught TypeError: Cannot instantiate class Outer\PrivateInner from the global scope + + +Nested classes are just regular class definitions with visibility. That means, except where otherwise noted, "how would nested classes work in situation X?" can be answered with "the same as any other class/object." + +==== Use-cases and examples ==== + +Nested classes are a powerful tool for organizing code and encapsulating functionality. Some use-cases include: + + * **Builder patterns**: Nested classes can be used to create complex objects step-by-step, encapsulating the construction logic within the outer class. + * **Data Transfer Objects (DTOs)**: Nested classes can be used to define data structures that are closely related to the outer class, improving code organization and readability. + * **Serialization helpers**: Nested classes can be used to define custom serialization logic for the outer class, encapsulating the serialization details within the outer class. + * **Encapsulation**: Nested classes can be used to encapsulate functionality that is only relevant to the outer class, reducing the visibility of the nested class and improving code organization. + * **Namespace management**: Nested classes can help reduce the number of top-level classes in a namespace, improving code organization and reducing clutter. + +=== Example: Serialization DTOs === + + +class Message { + public function __construct(private string $message, private string $from, private string $to) {} + + public function Serialize(): string { + return serialize(new SerializedMessage($this)); + } + + private class SerializedMessage { + public string $message; + public string $from; + public string $to; + + public function __construct(Message $message) { + $this->message = $message->message; + $this->from = $message->from; + $this->to = $message->to; + } + } +} + + +=== Example: Builder Pattern === + + +class Person { + public class Builder { + use AgeCalculator; + + private string $name; + private DateTimeInterface $age; + + public function setName(string $name): self { + $this->name = $name; + return $this; + } + + public function setBirthday(DateTimeInterface $age): self { + $this->age = $age; + return $this; + } + + public function build(): Person { + return new Person($this->name, $this->calculateAge($this->age)); + } + } + + public function __construct(private string $name, private int $age) {} +} + + +=== Example: DTOs === + + +enum Status { + case Active; + case Inactive; + + private enum Processing { + case Pending; + case Completed; + } + + public class Message { + + private Processing $processing = Processing::Pending; + + public function __construct(private Status $status) {} + + public function getMessage(): string { + return match ([$this->status, $this->processing]) { + [Status::Active, Processing::Pending] => "Active and Pending", + [Status::Active, Processing::Completed] => "Active and Completed", + [Status::Inactive, Processing::Pending] => "Inactive and Pending", + [Status::Inactive, Processing::Completed] => throw new LogicException('should not happen'), + }; + } + } + + public function createMessage(): Message { + return new Message($this); + } +} + + +=== Example: class shared via interface === + + +interface Reader { + public class Buffer { + public string $bytes; + } + + public function read(): void; + public function getBuffer(): Buffer; +} + +// in another file: +use Reader\Buffer; + +class FileReader implements Reader { + protected Buffer $buffer; + + public function __construct() { + $this->buffer = new Buffer(); + } + + public function read(): void { + $this->buffer->bytes = random_bytes(5); + } + + public function getBuffer(): Buffer { + return $this->buffer; + } +} + + +==== Why PHP needs nested classes ==== + +PHP developers today often simulate nested classes through long namespace chains or anonymous classes embedded in methods. While these patterns work, they come with visibility challenges, verbosity, and a lack of cohesion between related types. Nested classes solve these problems natively, offering better encapsulation without requiring new runtime behavior or complex inheritance tricks. + +==== Declaration Syntax ==== + +Declaring a nested class uses the same syntax as declaring a top-level class. You can define ''%%readonly%%'', ''%%final%%'', and ''%%abstract%%'' nested classes. In addition, nested classes may be declared with visibility modifiers (''%%private%%'', ''%%protected%%'', ''%%public%%''), which determine where the nested class can be referenced or instantiated. + +If a nested class does not explicitly declare visibility (private, protected, or public), it is implicitly considered public. This matches PHP’s existing class behavior for methods and properties. + + +class Outer { + private class Inner {} + protected readonly class ReadOnlyInner {} + public abstract class AbstractInner {} + public final class FinalInner {} +} + + +Outer classes remain "public" (accessible and visible to all), and visibility modifiers cannot be applied to them. + +==== Visibility ==== + +Visibility refers to whether a method, class, or property is accessible from outside its defining context. A nested class may be declared as ''%%private%%'', ''%%protected%%'', or ''%%public%%''. A private class will //only// be visible within the lexical outer class, while a protected class is visible both inside its lexical scope and any child classes. A public class has no restrictions and will be visible from anywhere in the codebase. + +=== Properties and Methods === + +All outer class’s properties and methods are visible from nested classes, as from the perspective of the nested class, it is a member of the outer class. However, unlike some languages with nested classes, an outer class does not have direct access to a nested class’s private or protected members. + +=== Method return type and argument declarations === + +Nested classes may only be used as a return type or argument declarations for methods and functions that have the same visibility or less. Thus returning a ''%%protected%%'' type from a ''%%public%%'' method is not allowed, but is allowed from a ''%%protected%%'' or ''%%private%%'' method. + +^Inner Class Visibility^Method Visibility^Allowed^ +|''%%public%%'' |''%%public%%'' |Yes | +|''%%public%%'' |''%%protected%%''|Yes | +|''%%public%%'' |''%%private%%'' |Yes | +|''%%protected%%'' |''%%public%%'' |No | +|''%%protected%%'' |''%%protected%%''|Yes | +|''%%protected%%'' |''%%private%%'' |Yes | +|''%%private%%'' |''%%public%%'' |No | +|''%%private%%'' |''%%protected%%''|No | +|''%%private%%'' |''%%private%%'' |Yes | + +Attempting to declare a return of a non-visible type will result in a ''%%TypeError%%'': + + +class Outer { + private class Inner {} + + public function getInner(): Inner { + return new Inner(); + } +} + +// Fatal error: Uncaught TypeError: Public method getInner cannot return private class Outer\Inner +new Outer()->getInner(); + + +This does not prevent the programmer from returning a hidden type, only from declaring it. The programmer may choose to use an interface or public type instead: + + +interface FooBar {} + +class Outer { + private class Inner implements FooBar {} + + public function getInner(): FooBar { + return new Inner(); + } +} + + +=== Properties === + +The visibility of a type declaration on a property must also not increase the visibility of a nested class. Thus, a public property cannot declare a private type. + + +class Outer { + private class Inner implements FooBar {} + + public function getInner(): FooBar { + return new Inner(); // not an error + } +} + + +==== Accessing outer classes ==== + +Nested classes do not implicitly have access to an outer class instance. This design choice avoids implicit coupling between classes and keeps nested classes simpler, facilitating potential future extensions (such as adding an explicit outer keyword) without backward compatibility issues. + +However, a programmer may pass an instance of an outer class (or another nested class) to a nested class. If a class has access to the outer class, it may use the outer class’s private, protected, and public properties and methods. + + +class Message { + public function __construct(private string $message, private string $from, private string $to) {} + + public function Serialize(): string { + return new SerializedMessage($this)->message; + } + + private class SerializedMessage { + public string $message; + + public function __construct(Message $message) { + $this->message = strlen($message->from) . $message->from . strlen($message->to) . $message->to . strlen($message->message) . $message->message; + } + } +} + + +==== Resolution ==== + +From a resolution perspective, outer classes behave as a pseudo-namespace. Using a short name of a nested class will resolve to the nested class. To disambiguate between different classes with the same name in the namespace and the nested class, the programmer can use fully qualified names. + + +class Inner {} + +abstract class Outer { + class Inner {} + public function foo(Inner $i); // resolves to Outer\Inner + public function bar(\Inner $i); // resolves to global Inner +} + + +This applies to deeply nested classes as well: + + +class Outer { + class Middle { + class Inner {} + } + public function foo(Middle\Inner $i); // resolves to Outer\Middle\Inner +} + + +This can lead to accidentally shadowing a class in the same namespace, thus requiring the use of ''%%use%%'' statements or fully qualified names to disambiguate. + +==== Reflection ==== + +Several new methods are added to ''%%ReflectionClass%%'' to help support inspection of nested classes: + + +$reflection = new ReflectionClass('\a\namespace\Outer\Inner'); + +$reflection->isNestedClass(); // true +$reflection->isPublic() || $reflection->isProtected() || $reflection->isPrivate(); // true +$reflection->getName(); // \a\namespace\Outer\Inner +$reflection->getShortName(); // Inner + + +For non-nested classes, ''%%ReflectionClass::isNestedClass()%%'' returns ''%%false%%''. + +A future addition to ''%%ReflectionClass::getNestedClasses()%%'' may allow enumeration of nested class declarations. + +==== Autoloading ==== + +Since nested classes are referenced using standard -separated names, the engine treats them like any other class name during resolution. If the outer class has not yet been loaded, PHP will attempt to autoload the nested class name, which may result in fallback behavior depending on the autoloader. Implementers are encouraged to structure autoloaders to load the outer class when a nested class is referenced. + +==== Usage ==== + +Nested classes may be defined in the body of any class-like structure, except traits: + + * in a class body + * in an anonymous class body + * in an enum body + * in an interface body (public nested classes only) + +Traits are not allowed to contain nested classes and are left to a future RFC. Traits are excluded from this RFC because nested class resolution via use statements introduces ambiguities about scope and visibility. These concerns are deferred to a future RFC. + +==== Outer class effects ==== + +Nested classes are fully independent of their outer class’s declaration modifiers. Outer class modifiers such as abstract, final, or readonly do not implicitly cascade down or affect the nested classes defined within them. + +Specifically: + + * An abstract outer class can define concrete (non-abstract) nested classes. Nested classes remain instantiable, independent of the outer class’s abstractness. + * A final outer class does not force its nested classes to be final. Nested classes within a final class can be extended internally, providing flexibility within encapsulation boundaries. + * A readonly outer class can define mutable nested classes. This supports internal flexibility, allowing the outer class to maintain immutability in its external API while managing state changes internally via nested classes. + +Examples: + + +abstract class Service { + // Valid: Nested class is not abstract, despite the outer class being abstract. + public class Implementation { + public function run(): void {} + } +} + +// Allowed; abstract outer class does not force nested classes to be abstract. +new Service\Implementation(); + + + +readonly class ImmutableCollection { + private array $items; + + public function __construct(array $items) { + $this->items = $items; + } + + public function getMutableBuilder(): Builder { + return new Builder($this->items); + } + + public class Builder { + private array $items; + + public function __construct(array $items) { + $this->items = $items; + } + + public function add(mixed $item): self { + $this->items[] = $item; + return $this; + } + + public function build(): ImmutableCollection { + return new ImmutableCollection($this->items); + } + } +} + + +In this example, even though ''%%Builder%%'' is a mutable nested class within a readonly outer class, the outer class maintains its immutability externally, while nested classes help internally with state management or transitional operations. + +=== Interfaces === + +Nested classes (including enums) may be defined in an interface body; however, they must be public classes. This is because an interface always defines public methods and properties, so this is to maintain consistency. + +==== Abstract nested classes ==== + +It is worth exploring what an ''%%abstract%%'' nested class means and how it works. Abstract nested classes are allowed to be the parent of any class that can see them. For example, a private abstract class may only be inherited by subclasses in the same outer class. A protected abstract class may be inherited by a nested class in a subclass of the outer class or a nested class in the same outer class. However, this is not required by subclasses of the outer class. + +Abstract nested classes may not be instantiated, just as abstract outer classes may not be instantiated. + + +class OuterParent { + protected abstract class InnerBase {} +} + +// Middle is allowed, does not have to implement InnerBase +class Middle extends OuterParent {} + +// Last demonstrates extending the abstract inner class explicitly. +class Last extends OuterParent { + private abstract class InnerExtended extends OuterParent\InnerBase {} +} + + +===== Backward Incompatible Changes ===== + + * This RFC introduces new syntax and behavior to PHP, which does not conflict with existing syntax. + * Some error messages will be updated to reflect nested classes, and tests that depend on these error messages are likely to fail. + * Tooling using AST or tokenization may need to be updated to support the new syntax. + +Note: Applying visibility modifiers to class declarations is new syntax, but only valid in nested contexts. Top-level classes will continue to behave as before, and this change introduces no ambiguity in existing code. + +===== Proposed PHP Version(s) ===== + +This RFC targets the next version of PHP. + +===== RFC Impact ===== + +==== To SAPIs ==== + +None. + +==== To Existing Extensions ==== + +None were discovered during testing, but it is possible there are unbundled extensions that may be affected. + +==== To Opcache ==== + +This change introduces a new opcode, AST, and other changes that affect opcache. These changes are included as part of the PR that implements this feature. + +==== To Tooling ==== + +Tooling that uses AST or tokenization may need to be updated to support the new syntax, such as syntax highlighting, static analysis, and code generation tools. + +===== Open Issues ===== + +Pending discussion. + +===== Unaffected PHP Functionality ===== + +There should be no change to any existing PHP syntax. + +===== Future Scope ===== + + * Inner interfaces + * Inner traits + +This RFC focuses on class and enum nesting. Interfaces and traits are excluded for now due to their unique visibility and resolution semantics, but may be considered in follow-up RFCs. + +===== Proposed Voting Choices ===== + +As this is a significant change to the language, a 2/3 majority is required. + + +===== Patches and Tests ===== + +To be completed. + +===== Implementation ===== + +===== References ===== + + * [[https://externals.io/message/125975#125977|email that inspired this RFC]] + * [[https://wiki.php.net/rfc/records|RFC: Records]] + +===== Rejected Features ===== + +TBD diff --git a/published/support.ptxt b/published/support.ptxt new file mode 100644 index 0000000..2b7560a --- /dev/null +++ b/published/support.ptxt @@ -0,0 +1,58 @@ +====== Supporting a transition from V2 to V3 ====== + +===== Introduction ===== + +On July 1, 2025, we aim to shut down V2 forever. With enough of a warning, we can delay the shutdown until December 2025; however, this incurs additional costs to us that we would like to avoid. We need to know this by May 31, 2025. + +There are a number of impacted products and services: + + * white-label apps + * API customers + * in-gym solutions + +===== White-label app options ===== + +Tenants still on V2 have several options available to them: + + - Stop using Funxtion. + - Upsell via SDK and upgrade to V3. + - Purchase our V3 whitelabel app via Leisure Labs. + +==== Option 1: Stop using Funxtion ==== + +Tenants may opt to stop using Funxtion altogether. This is the simplest solution but also the most drastic. + +==== Option 2: Upsell via SDK and upgrade to V3 ==== + +Tenants may opt to migrate to V3 through using our SDK. They will be responsible for their own development and maintenance. + +See the migration section for more details. + +==== Option 3: Purchase our V3 whitelabel app via Leisure Labs ==== + +Tenants may opt to purchase our V3 whitelabel app via Leisure Labs. + +===== API customer options ===== + +API customers have a limited set of options available to them: + + - Stop using Funxtion. + - Manually migrate content to V3 and use our SDK. + +==== Option 1: Stop using Funxtion ==== + +API customers may opt to stop using Funxtion altogether. This is the simplest solution but also the most drastic. + +==== Option 2: Manually migrate content to V3 and use our SDK ==== + +No automated migration options exist for API customers. They must manually migrate their content to V3 and use our SDK. + +===== In-gym solutions ===== + +In-gym solutions use the V2 API and are not compatible with V3. The development team aims to have a solution in-place by July 1, 2025, and expects that it will be a seamless transition by working with Notice Media to provide V3 compatibility and/or other means. + +===== Migration ===== + +Through the migration of GoodLife to V3, we have some basic tooling available to assist with migrations from V2 to V3. However, this still requires a lot of manual work to get it right per-tenant. Thus, if a tenant requests that we migrate their content, please ensure that we bill for the hours spent working on this. + +Alternatively, the tenant can migrate their content themselves through the use of the Portal. diff --git a/published/template.ptxt b/published/template.ptxt index 3f379b3..fe97dc4 100644 --- a/published/template.ptxt +++ b/published/template.ptxt @@ -1,17 +1,21 @@ ====== PHP RFC: Your Title Here ====== -* Version: 0.9 * Date: 2013-02-24 (use today's date here) * Author: Your Name, * Status: Draft (or Under Discussion or Accepted or Declined) * First Published at: http://wiki.php.net/rfc/your_rfc_name + * Version: 0.9 + * Date: 2013-02-24 (use today's date here) + * Author: Your Name, + * Status: Draft (or Under Discussion or Accepted or Declined) + * First Published at: http://wiki.php.net/rfc/your_rfc_name This is a suggested template for PHP Request for Comments (RFCs). Change this template to suit your RFC. Not all RFCs need to be tightly specified. Not all RFCs need all the sections below. Read https://wiki.php.net/rfc/howto carefully! Quoting [[http://news.php.net/php.internals/71525|Rasmus]]: > PHP is and should remain: -> + > 1) a pragmatic web-focused language -> + > 2) a loosely typed language -> + > 3) a language which caters to the skill-levels and platforms of a wide range of users Your RFC should move PHP forward following his vision. As [[http://news.php.net/php.internals/66065|said by Zeev Suraski]] "Consider only features which have significant traction to a large chunk of our userbase, and not something that could be useful in some extremely specialized edge cases [...] Make sure you think about the full context, the huge audience out there, the consequences of making the learning curve steeper with every new feature, and the scope of the goodness that those new features bring." diff --git a/src/convert-from-md.sh b/src/convert-from-md.sh index d98c6c5..5bf4e72 100755 --- a/src/convert-from-md.sh +++ b/src/convert-from-md.sh @@ -1,6 +1,6 @@ #!/bin/sh -docker run -v "$(pwd)":/data --pull always --user "$(id -u)":"$(id -g)" pandoc/latex -t dokuwiki -f gfm "$1" -o "$2" +docker run --rm -v "$(pwd)":/data --pull always --user "$(id -u)":"$(id -g)" pandoc/latex -t dokuwiki -f gfm "$1" -o "$2" # remove all and tags sed -i 's///g' "$2" sed -i 's/<\/HTML>//g' "$2"