New PHP version! (8.4)
You may have heard
You know what that means...
New Toys!
Asymmetric visibility
Interface properties
Property hooks (accessors)
And other stuff we're not going to talk about
Go see the change log and docs
Only going to skim over the syntax, go read the docs
Asymmetric visibility
class Person {
public private (set) string $fullName ;
public function __construct (
public private (set ) string $firstName ,
public private (set ) string $lastName ,
public protected (set ) int $age ,
) {
$this ->fullName = sprintf ('%s %s' , $this ->firstName, $this ->lastName);
}
public function update (string $first , string $last ): void {
$this ->first = $first ;
$this ->last = $last ;
$this ->fullName = sprintf ('%s %s' , $this ->firstName, $this ->lastName);
}
}
More flexible than readonly
Vary the read/write side independently
Asymmetric visibility
class Person {
private (set) string $fullName ;
public function __construct (
private (set ) string $firstName ,
private (set ) string $lastName ,
protected (set ) int $age ,
) {
$this ->fullName = sprintf ('%s %s' , $this ->firstName, $this ->lastName);
}
public function update (string $first , string $last ): void {
$this ->first = $first ;
$this ->last = $last ;
$this ->fullName = sprintf ('%s %s' , $this ->firstName, $this ->lastName);
}
}
Optionally omit public
Interface properties
interface Named {
public string $firstName { get; }
public string $lastName { get; }
public string $nickname { get; set; }
public function fullName ( ): string ;
}
Interfaces can now require properties. Read or write or both.
Can satisfy the interface however you want.
Interface properties
interface Named {
public string $firstName { get; }
public string $lastName { get; }
public string $nickname { get; set; }
public function fullName ( ): string ;
}
class Person implements Named {
public private (set) string $firstName ;
public string $lastName ;
public string $nickname ;
public function fullName ( ): string {
sprintf ('%s %s' , $this ->firstName, $this ->lastName);
}
}
$firstName is public read, all we need.
$lastName is public read/write, which is also OK
$nickname is a basic property, so works read or write.
Property hooks (aka Accessors)
class Person {
public string $name = '' {
get {
return $this ->name;
}
set (string $value ) {
$this ->name = $value ;
}
}
}
$p = new Person ();
$p ->name = "Larry" ;
print $p ->name . PHP_EOL;
Hooks intercept the read/write process
Override the normal behavior with arbitrary code
Same net effect as a method, but accessed through property syntax
set args are optional
class Person {
public string $name = '' {
get {
return $this ->name;
}
set {
$this ->name = $value ;
}
}
}
$p = new Person ();
$p ->name = "Larry" ;
print $p ->name . PHP_EOL;
Don't need to specify the type/name of the set arg. Defaults to $value and prop type
Why $value? It's what other langs use for this.
Short-hand get
class Person {
public string $name = '' {
get => $this ->name;
set {
$this ->name = $value ;
}
}
}
$p = new Person ();
$p ->name = "Larry" ;
print $p ->name . PHP_EOL;
Same idea as short-closures
Short-hand set
class Person {
public string $name = '' {
get => $this ->name;
set => $value ;
}
}
$p = new Person ();
$p ->name = "Larry" ;
print $p ->name . PHP_EOL;
Return value from short-set gets assigned to property.
Still haven't changed any behavior in this code, but shown skeleton
But... why?
class Person {
private (set) string $fullName ;
public function __construct (
public string $firstName ,
public string $lastName ,
public int $age ,
) {
$this ->fullName = sprintf ('%s %s' , $this ->firstName, $this ->lastName);
}
}
$p = new Person ('James' , 'Kirk' );
$p ->lastName = 'garfield' ;
print $p ->fullName . PHP_EOL;
Now want to capitalize the first and last names automatically
But... why?
class Person {
private (set) string $fullName ;
public function __construct (
private string $firstName ,
private string $lastName ,
private int $age ,
) {
$this ->fullName = sprintf ('%s %s' , $this ->firstName, $this ->lastName);
}
public function getFirstName ( ): string {
return $this ->firstName;
}
public function setFirstName (string $name ): void {
$this ->firstName = ucfirst ($name );
$this ->fullName = sprintf ('%s %s' , $this ->firstName, $this ->lastName);
}
}
$p = new Person ('James' , 'Kirk' );
$p ->setLastName ('garfield' );
print $p ->getFullName () . PHP_EOL;
This is what we used to have to do, just in case.
API break to add the methods later.
How often did you add stuff to the methods later?
No more boilerplate!
class Person {
private (set) string $fullName {
get => sprintf ('%s %s' , $this ->firstName, $this ->lastName);
}
public function __construct (
public string $firstName { set => ucfirst($value ); },
public string $lastName { set => ucfirst($value ); },
public int $age ,
) {}
}
$p = new Person ('James' , 'Kirk' );
$p ->lastName = 'garfield' ;
print $p ->fullName . PHP_EOL;
class Person {
private (set) string $fullName {
get => sprintf ('%s %s' , $this ->firstName, $this ->lastName);
}
public function __construct (
public string $firstName { set => ucfirst($value ); },
public string $lastName { set => ucfirst($value ); },
public int $age ,
) {}
}
$p = new Person ('James' , 'Kirk' );
$p ->lastName = 'garfield' ;
print $p ->fullName . PHP_EOL;
class Person {
private (set) string $fullName {
get => sprintf ('%s %s' , $this ->firstName, $this ->lastName);
}
public function __construct (
public string $firstName { set => ucfirst($value ); },
public string $lastName { set => ucfirst($value ); },
public int $age ,
) {}
}
$p = new Person ('James' , 'Kirk' );
$p ->lastName = 'garfield' ;
print $p ->fullName . PHP_EOL;
Can now sneak in changes without so much upfront-work.
Just add set hook to property, done
$fullName is a virtual property, computed on the fly.
This is the reason stated in the RFC.
But... why?
class Person {
private (set) string $fullName {
get => sprintf ('%s %s' , $this ->firstName, $this ->lastName);
}
public string $firstName {
set => ucfirst ($value );
},
public string $lastName {
set => ucfirst ($value );
},
public function __construct (
string $first ,
string $last ,
public int $age ,
) {
$this ->firstName = $first ;
$this ->lastName = $last ;
}
}
$p = new Person ('James' , 'Kirk' );
$p ->lastName = 'garfield' ;
print $p ->fullName . PHP_EOL;
Hooks in the constructor are OK if short, but don't have to.
Hooks are an escape hatch
Don't use them
OK to make properties public (read or write)
Use hooks to preserve BC
No more stupid get/set methods!
If you stop here, that's OK!
What is an object?
Product type
Named tuple of typed values. Often exposed: Point(x, y)
Opaque Value objects
Hidden internals; DateTime, Money, Request; often immutable
Entity
Classic OOP academia; often field/get/set. Cat extends Animal
Closures with funny syntax
AKA "service objects"; no data, just functions and dependencies
No one definition of this, either
Product meaning cardinality is "product" (times) the cardinality of members
Properties are applicable for all of them
What is a Property
A Property is a logical aspect or feature of an object
Don't care about the underlying details anymore
Fields are an implementation detail, Properties are an interface
Properties
Ask an object about itself
Tell an object about itself
If what you're doing is one of those, it may be a property
Whether there is additional logic involved is now irrelevant!
For example
$timestamp ->timezone;
$timestamp ->timezone = new DateTimeZone ('America/Chicago' );
$entity ->id;
$list ->size;
$comment ->post;
$timestamp ->timezone;
$timestamp ->timezone = new DateTimeZone ('America/Chicago' );
$entity ->id;
$list ->size;
$comment ->post;
$timestamp ->timezone;
$timestamp ->timezone = new DateTimeZone ('America/Chicago' );
$entity ->id;
$list ->size;
$comment ->post;
$timestamp ->timezone;
$timestamp ->timezone = new DateTimeZone ('America/Chicago' );
$entity ->id;
$list ->size;
$comment ->post;
$timestamp ->timezone;
$timestamp ->timezone = new DateTimeZone ('America/Chicago' );
$entity ->id;
$list ->size;
$comment ->post;
$timestamp ->timezone;
$timestamp ->timezone = new DateTimeZone ('America/Chicago' );
$entity ->id;
$list ->size;
$comment ->post;
How are these implemented internally? ¯\_(ツ)_/¯
Don't think about implementation, think about conceptualization
What value do the words "get" and "set" add over language syntax? None!
<aside>
Pop quiz: What is the second-longest entry in the OED?
"get"
Pop quiz: What is the longest entry in the OED?
"set"
These words mean everything, so they mean nothing
</aside>
Oxford English Dictionary
Dozens of pages each!
Use case: Filesystem abstraction
Files
contact.md
contact.php
contact.latte
All represented by the logical Page "contact"
(Real code from a side project, MiDy)
Data object interface
interface Page {
public string $title { get; }
public string $summary { get; }
public array $tags { get; }
public bool $hidden { get; }
public bool $routable { get; }
public DateTimeImmutable $publishDate { get; }
public DateTimeImmutable $lastModifiedDate { get; }
public LogicalPath $path { get; }
public PhysicalPath $physicalPath { get; }
public string $folder { get; }
}
All pages must have these aspects/features/properties
None need be writeable!
Real example from real code, only slight simplification
Data object interface
interface Page {
public string $title { get; }
public string $summary { get; }
public array $tags { get; }
public bool $hidden { get; }
public bool $routable { get; }
public DateTimeImmutable $publishDate { get; }
public DateTimeImmutable $lastModifiedDate { get; }
public LogicalPath $path { get; }
public PhysicalPath $physicalPath { get; }
public string $folder { get; }
}
class PageRecord implements Page {
public function __construct (
private (set ) string $title ,
private (set ) string $summary ,
private (set ) DateTimeImmutable $publishDate ,
// Implementation detail for this class .
private array $files ,
// ...
) {}
}
PageRecord wraps multiple files into one logical page
Could also use readonly if you prefer.
What would methods add? Nothing!
Lazily computed values
class PageRecord implements Page {
public PhysicalPath $physicalPath ;
public function __construct (
private (set ) array $files ,
// ...
) {}
}
class PageRecord implements Page {
public PhysicalPath $physicalPath ;
public function __construct (
private (set ) array $files ,
// ...
) {}
}
Focus on $physicalPath
How to derive $physicalPath from files list?
Lazily computed values
class PageRecord implements Page {
private File $activeFile {
get => $this ->activeFile ??= array_values ($this ->files)[0 ];
}
public PhysicalPath $physicalPath {
get => $this ->activeFile->physicalPath;
}
public function __construct (
private (set ) array $files ,
// ...
) {}
}
class PageRecord implements Page {
private File $activeFile {
get => $this ->activeFile ??= array_values ($this ->files)[0 ];
}
public PhysicalPath $physicalPath {
get => $this ->activeFile->physicalPath;
}
public function __construct (
private (set ) array $files ,
// ...
) {}
}
class PageRecord implements Page {
private File $activeFile {
get => $this ->activeFile ??= array_values ($this ->files)[0 ];
}
public PhysicalPath $physicalPath {
get => $this ->activeFile->physicalPath;
}
public function __construct (
private (set ) array $files ,
// ...
) {}
}
class PageRecord implements Page {
private File $activeFile {
get => $this ->activeFile ??= array_values ($this ->files)[0 ];
}
public PhysicalPath $physicalPath {
get => $this ->activeFile->physicalPath;
}
public function __construct (
private (set ) array $files ,
// ...
) {}
}
$activeFile is computed on-the-fly and cached
Fully transparent
Other code can use it as though it were just there.
Changing definition of "Active" is one line.
Could $activeFile and $physicalPath be methods? Yes, but why? They're properties of this page.
Lazily computed values
class PageRecord implements Page {
private File $activeFile ;
private function activeFile ( ): File {
return $this ->activeFile ??= array_values ($this ->files)[0 ];
}
public PhysicalPath $physicalPath {
get => $this ->activeFile ()->physicalPath;
}
public function __construct (
private (set ) array $files ,
// ...
) {}
}
Even as a method, still need a place to cache it.
Keeping the property and method separate serves no purpose.
Postel's law
Be conservative in what you send, be liberal in what you accept.
class PageRecord implements Page {
private (set) LogicalPath $logicalPath {
set (LogicalPath|string $value )
=> is_string ($value ) ? LogicalPath ::create ($value ) : $value ;
}
public function __construct (
LogicalPath|string $logicalPath ,
private (set ) array $files ,
// ...
) {
$this ->logicalPath = $logicalPath ;
}
}
class PageRecord implements Page {
private (set) LogicalPath $logicalPath {
set (LogicalPath|string $value )
=> is_string ($value ) ? LogicalPath ::create ($value ) : $value ;
}
public function __construct (
LogicalPath|string $logicalPath ,
private (set ) array $files ,
// ...
) {
$this ->logicalPath = $logicalPath ;
}
}
class PageRecord implements Page {
private (set) LogicalPath $logicalPath {
set (LogicalPath|string $value )
=> is_string ($value ) ? LogicalPath ::create ($value ) : $value ;
}
public function __construct (
LogicalPath|string $logicalPath ,
private (set ) array $files ,
// ...
) {
$this ->logicalPath = $logicalPath ;
}
}
This is also a form of validation!
Can't use promotion here, because cannot have a wider set type that way.
LogicalPath::create() won't succeed unless $value is a correct string.
Database record hydration
class PageRecord implements Page {
private (set) LogicalPath $logicalPath {
set (LogicalPath|string $value )
=> is_string ($value ) ? LogicalPath ::create ($value ) : $value ;
}
public function __construct (
LogicalPath|string $logicalPath ,
private (set ) array $files ,
// ...
) {
$this ->logicalPath = $logicalPath ;
}
}
class PageRepository {
public function readPage (LogicalPath $path ): ?PageRecord {
$result = $this ->conn->query (, [$path ])->queryOne ();
return $result ? new PageRecord (...$result ) : null ;
}
}
Any upcasting of value objects happens inside PageRecord, where it belongs!
LogicalPath is Stringable, so can use in the query params.
This is all the read model. What is the write model?
Enum hydration
enum SortOrder {
case Asc = 'asc' ;
case Desc = 'desc' ;
}
class SomeClass {
public SortOrder $order {
set (SortOrder|string $value )
=> is_string ($value ) ? SortOrder ::from ($value ) : $value ;
}
}
Only store the enum, but set via enum or string.
Could add method to SortOrder to handle any case.
Database record derivation
class PageData {
public string $title {
get => $this ->values (__PROPERTY__)[0 ];
}
public bool $hidden {
get => array_all ($this ->values (__PROPERTY__), static fn($x ): bool => (bool )$x );
}
public \DateTimeImmutable $publishDate {
get => \max ($this ->values (__PROPERTY__));
}
public array $files {
get => array_map (static fn(ParsedFile $f ) => $f ->toFile (), $this ->parsedFiles);
}
public function __construct (
public LogicalPath $logicalPath ,
private array $parsedFiles ,
) {}
private function values (string $property ): array {
return array_column ($this ->parsedFiles, $property );
}
}
Write model is all derived off of other values.
Could also be done with methods.
Which is better? Debatable.
Use case: HTTP modeling
Requests have lots of properties/attributes/things about them
HTTP Request / PSR-7 redux
interface ServerRequestInterface {
public function getProtocolVersion ( ): string ;
public function getBody ( ): StreamInterface ;
public function getMethod ( ): string ;
public function getUri ( ): Uri ;
public function getParsedBody ( ): null |array |object ;
public function getHeaders ( ): array ;
public function getAttributes ( ): array ;
public function hasHeader (string $name ): bool ;
public function getHeader (string $name ): array ;
public function getHeaderLine (string $name ): string ;
public function getAttribute (string $name , mixed $default = null ): mixed ;
}
with() methods don't modify in place, require args, so still methods
Added types for clarity
What could it look like today?
HTTP Request modern version
interface ServerRequestInterface {
public string $protocolVersion { get; }
public StreamInterface $body { get; }
public string $method { get; }
public Uri $uri { get; }
public null |array |object $parsedBody { get; }
public array $headers { get; }
public array $attributes { get; }
public function hasHeader (string $name ): bool ;
public function getHeader (string $name ): array ;
public function getHeaderLine (string $name ): string ;
public function getAttribute (string $name , mixed $default = null ): mixed ;
}
interface ServerRequestInterface {
public string $protocolVersion { get; }
public StreamInterface $body { get; }
public string $method { get; }
public Uri $uri { get; }
public null |array |object $parsedBody { get; }
public array $headers { get; }
public array $attributes { get; }
public function hasHeader (string $name ): bool ;
public function getHeader (string $name ): array ;
public function getHeaderLine (string $name ): string ;
public function getAttribute (string $name , mixed $default = null ): mixed ;
}
interface ServerRequestInterface {
public string $protocolVersion { get; }
public StreamInterface $body { get; }
public string $method { get; }
public Uri $uri { get; }
public null |array |object $parsedBody { get; }
public array $headers { get; }
public array $attributes { get; }
public function hasHeader (string $name ): bool ;
public function getHeader (string $name ): array ;
public function getHeaderLine (string $name ): string ;
public function getAttribute (string $name , mixed $default = null ): mixed ;
}
Just change the spelling for the easy ones. Make the class readonly.
These are all aspects of the request. "Properties" of it.
Cannot do headers or attributes, as those have arguments.
Those are an operation *on* the property.
getHeader() vs getHeaderLine(): Different representations of the data, so can't have a single property.
HTTP Request, modern version
interface ServerRequestInterface {
public string $protocolVersion { get; }
public StreamInterface $body { get; }
public string $method { get; }
public Uri $uri { get; }
public null |array |object $parsedBody { get; }
public array $headers { get; }
public array $attributes { get; }
}
class Header {
public array $raw { get => ... }
public string $line { get => ... }
}
$value = $request ->headers['user-agent' ]?->line;
$hasHeader = isset ($request ->headers['user-agent' ]);
Header has different representations, so those are separate properties.
Virtual properties, do as you will.
hasHeader() could be just an isset check, as above with nullsafe method.
Not sold on this myself, but it's a valid option.
HTTP Request, Tempest framework
interface Request {
public Method $method { get; }
public string $uri { get; }
public ?string $raw { get; }
public array $body { get; }
public RequestHeaders $headers { get; }
public string $path { get; }
public array $query { get; }
public array $files { get; }
public array $cookies { get; }
public function has (string $key ): bool ;
public function hasBody (string $key ): bool ;
public function hasQuery (string $key ): bool ;
public function get (string $key , mixed $default = null ): mixed ;
public function getSessionValue (string $name ): mixed ;
public function getCookie (string $name ): ?Cookie ;
}
That is what Temptest did!
RequestHeaders is list object, ArrayAccess, iterable
Also a Header class
Not sure if I like Tempest yet, but it does heavily use modern properties
It used to be anything interesting had to be a method
Now, design your API around concepts, not implementation
This isn't "exposing implementation". Properties still hide implementation!
Methods for no purpose but validation were exposing implementation!
Use case: Definition interfaces
Objects get registered with a service and tell the service about themselves
Definition interfaces
The traditional way
interface ParseProperties {
public function setProperties (array $properties ): void ;
public function includePropertiesByDefault ( ): bool ;
public function propertyAttribute ( ): string ;
}
#[\Attribute ]
class ClassWithProperties implements ParseProperties {
public readonly array $properties = [];
public function __construct (
public readonly int $a = 0 ,
) {}
public function setProperties (array $properties ): void {
$this ->properties = $properties ;
}
public function includePropertiesByDefault ( ): bool {
return true ;
}
public function propertyAttribute ( ): string {
return BasicProperty ::class ;
}
}
interface ParseProperties {
public function setProperties (array $properties ): void ;
public function includePropertiesByDefault ( ): bool ;
public function propertyAttribute ( ): string ;
}
#[\Attribute ]
class ClassWithProperties implements ParseProperties {
public readonly array $properties = [];
public function __construct (
public readonly int $a = 0 ,
) {}
public function setProperties (array $properties ): void {
$this ->properties = $properties ;
}
public function includePropertiesByDefault ( ): bool {
return true ;
}
public function propertyAttribute ( ): string {
return BasicProperty ::class ;
}
}
Methods: Stupidly verbose 90% of the time
But can make the values dynamic (eg, from constructor params)
Real code from Crell/AttributeUtils
Definition interfaces
The Laravel way
interface ParseProperties {
public function setProperties (array $properties ): void ;
}
#[\Attribute ]
class ClassWithProperties implements ParseProperties {
public array $properties = [];
public bool $includeByDefault = true ;
public string $propertyAttribute = BasicProperty ::class ;
public function __construct (
public readonly int $a = 0 ,
) {}
public function setProperties (array $properties ): void {
$this ->properties = $properties ;
}
}
interface ParseProperties {
public function setProperties (array $properties ): void ;
}
#[\Attribute ]
class ClassWithProperties implements ParseProperties {
public array $properties = [];
public bool $includeByDefault = true ;
public string $propertyAttribute = BasicProperty ::class ;
public function __construct (
public readonly int $a = 0 ,
) {}
public function setProperties (array $properties ): void {
$this ->properties = $properties ;
}
}
Great, now they're mutable.
No explicit definition for them.
Definition interfaces
The Symfony/Attributes way
interface ParseProperties {
public function setProperties (array $properties ): void ;
}
#[PropertiesToParse (
includeByDefault : true ,
propertyAttribute : BasicProperty ::class ,
)]
#[\Attribute ]
class ClassWithProperties implements ParseProperties {
public array $properties = [];
public function __construct (
public readonly int $a = 0 ,
) {}
public function setProperties (array $properties ): void {
$this ->properties = $properties ;
}
}
Not mutable, out of the code flow
But now need interface for behavior AND attribute for definition
Definition cannot be dynamic.
Definition interfaces
The PHP 8.4 way
interface ParseProperties {
public function setProperties (array $properties ): void ;
public bool $includePropertiesByDefault { get; }
public string $propertyAttribute { get; }
}
#[\Attribute ]
class ClassWithProperties implements ParseProperties {
public array $properties = [];
private (set) bool $includePropertiesByDefault = true ;
public bool $propertyAttribute { get => BasicProperty ::class ; }
public function __construct (
public readonly int $a = 0 ,
) {}
public function setProperties (array $properties ): void {
$this ->properties = $properties ;
}
}
interface ParseProperties {
public function setProperties (array $properties ): void ;
public bool $includePropertiesByDefault { get; }
public string $propertyAttribute { get; }
}
#[\Attribute ]
class ClassWithProperties implements ParseProperties {
public array $properties = [];
private (set) bool $includePropertiesByDefault = true ;
public bool $propertyAttribute { get => BasicProperty ::class ; }
public function __construct (
public readonly int $a = 0 ,
) {}
public function setProperties (array $properties ): void {
$this ->properties = $properties ;
}
}
Could use private-set or virtual prop to fulfill interface
private-set is faster, property read not method call
Definition interfaces
The dynamic way
interface ParseProperties {
public function setProperties (array $properties ): void ;
public bool $includePropertiesByDefault { get; }
public string $propertyAttribute { get; }
}
#[\Attribute ]
class ClassWithProperties implements ParseProperties {
public array $properties = [];
public bool $includePropertiesByDefault { get => $this ->include ; }
public bool $propertyAttribute { get => $this ->propertiesAs; }
public function __construct (
public readonly int $a = 0 ,
private readonly $include = true ;
private readonly $propertiesAs = BasicProperty ::class ,
) {}
public function setProperties (array $properties ): void {
$this ->properties = $properties ;
}
}
Can still just read the properties as normal.
Special case of BC shim, in a way.
Definition interfaces
The overdone way?
interface ParseProperties {
public array $properties { set };
public bool $includePropertiesByDefault { get; }
public string $propertyAttribute { get; }
}
#[\Attribute ]
class ClassWithProperties implements ParseProperties {
public array $properties {
set {
$this ->props = array_filter ($value , someFilter (...));
}
}
public bool $includePropertiesByDefault { get => $this ->include ; }
public bool $propertyAttribute { get => $this ->propertiesAs; }
public function __construct (
public readonly int $a = 0 ,
private readonly $include = true ;
private readonly $propertiesAs = BasicProperty ::class ,
) {}
}
interface ParseProperties {
public array $properties { set };
public bool $includePropertiesByDefault { get; }
public string $propertyAttribute { get; }
}
#[\Attribute ]
class ClassWithProperties implements ParseProperties {
public array $properties {
set {
$this ->props = array_filter ($value , someFilter (...));
}
}
public bool $includePropertiesByDefault { get => $this ->include ; }
public bool $propertyAttribute { get => $this ->propertiesAs; }
public function __construct (
public readonly int $a = 0 ,
private readonly $include = true ;
private readonly $propertiesAs = BasicProperty ::class ,
) {}
}
interface ParseProperties {
public array $properties { set };
public bool $includePropertiesByDefault { get; }
public string $propertyAttribute { get; }
}
#[\Attribute ]
class ClassWithProperties implements ParseProperties {
public array $properties {
set {
$this ->props = array_filter ($value , someFilter (...));
}
}
public bool $includePropertiesByDefault { get => $this ->include ; }
public bool $propertyAttribute { get => $this ->propertiesAs; }
public function __construct (
public readonly int $a = 0 ,
private readonly $include = true ;
private readonly $propertiesAs = BasicProperty ::class ,
) {}
}
A set-only property??? It's legal!
Not sure if I like this approach either, but it's now on the table
* Intro
* PHP has new features: hooks, interface props, aviz
* Very brief example of each
* Easy approach: don't use hooks, write less code, it's an escape hatch
* This is OK! If you stop here, you are not doing it wrong.
* ... But we can go further.
* What is a property?
* What is an object? (Philosoraptor)
* No one-true OOP
* Product types: internals exposed
* Compound types: internals hidden (eg, DateTime)
* Logical representation (classic OOP academia)
* Closures with funny syntax (services, internals hidden)
* Historically: All properties private always. Couldn't do better.
* "Language teaches you to not want what it doesn't offer." (find source)
* Property vs Field
* Wikipedia
* https://en.wikipedia.org/wiki/Property_(programming)
* https://en.wikipedia.org/wiki/Field_(computer_science)
* C#
* Kotlin
* Swift
* Others...
* What is a property? Depends on the type of object
* "Make one thing do many things" (find source)
* My def: "A visible aspect of a value."
* Method: "An action taken on a value."
* Must manipulating an aspect be a method? Depends who you ask...
* Things you CAN do with hooks
* BC shim
* Cached value
* Upcasting inside a value object upcast from an array record (eg, DB hydration)
*