Skip to content

Instantly share code, notes, and snippets.

@brzuchal
Last active March 19, 2021 20:03
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save brzuchal/09a2a3f0534016742550c28582945a7c to your computer and use it in GitHub Desktop.
PHP Structs Example
====== PHP RFC: Structs ======
* Version: 0.9
* Date: 2020-03-25
* Author: Michał Brzuchalski <brzuchal@php.net>
* Status: Draft
* First Published at: http://wiki.php.net/rfc/structs
Structures as a complex type solution for programmers in functional programming appears in few programming languages but differs in a solution taken there. Some languages like **C#** have both **classes** and **scructs** living aside in harmony.
In PHP we do have a way to express complex types using classes which additionally can implement a behaviour but in some cases, a complex type needs are nothing more than the strict list of fields and type restrictions and easy way to initialize the value reducing the boilerplate on the user side.
Aim of this RFC is to fill the gap which currently cannot be implemented using classes with respect to reducing verbosity to a minimum.
===== Introduction =====
Many programming languages with strong support for functional programming has a way to define complex types by the programmer.
These complex types are known as **structures**.
Programming languages with strong OOP paradigm uses classes as enhancements of structures cause they also consist of attached logic and different rules when instantiation, modification and assigning of those types happen.
In languages like **C**, **C++**, **C#** and **D** structures are value types which means when a struct is passed as an argument to a function any modifications to the struct in that function will not be reflected in the original variable (unless __pass-by-reference__ is used).
===== Proposal =====
A struct is a user-defined type that contains a collection of named fields.
A structure has different fields of the same or different type.
It is used to group related data together to form a single unit.
A struct is a user type consisting of a name and fields specification consisting of field names and corresponding types.
A struct cannot be null, and a struct variable cannot be assigned null unless the variable is declared as a nullable value type.
==== Differences between classes and structs ====
Structures differ from classes therefore they're not designed for inheritance.
Therefore it is not possible to extend struct type as in classes.
Unlike classes, structs can be instantiated without using a new operator and are passed over using by-value semantics.
==== Usage ====
A struct is used mainly when you need to define a complex type with no need for any additional logic going along with the value.
Therefore a struct is defined to not have an identity and that is why the implementation is free to make copies of the struct as convenient.
==== Value types ====
Structs are copied on assignment.
When a struct is assigned to a new variable, unlike classes, which are reference types, all the data is copied, and any modification to the new copy does not change the data for the original copy.
==== Declaration ====
A struct type is nothing but a schema containing the blueprint of a data a structure will hold.
Structs share most of the same syntax as classes.
The name of the struct must be a valid PHP identifier name.
To make things simple, it is possible to use type alias so that can refer to struct type easily.
The syntax to create a struct is as follows:
<code php>
struct StructName {
fieldType1 $field1;
fieldType2 $field2, $field3;
}
</code>
In the above syntax, `StructName` is a struct type while `$field1`, `$field2` and `$field3` are fields of `fieldType1` and `fieldType2` respectively.
<code php>
struct Employee {
string $firstName;
string $lastName;
int $salary;
bool $fullTime;
}
</code>
It is also possible to combine different fields of the same type in the same line and with default values as it is possible when declaring class properties, which is presented in the next example:
<code php>
struct Salary {
int $salary = 1000, $insurance = 50, $allowance = 50;
}
struct Employee {
string $firstName, $lastName;
Salary $salary = Salary { 1200, 0, 0 };
bool $fullTime = true;
}
</code>
==== Initialization ====
Concluding initialization of struct fields values can be done in a few ways:
* using default initializer with all values for fields in the right order enclosed by a curly brace without field names or with field names (then the order of them has no meaning);
* using custom initializer with values for fields passed in the order specified by initializer function as a values list enclosed in parentheses.
Working with struct without default values initialization could be hard when the amount of fields inside the struct is significant and some of them could possibly have default values cause they're not used so often.
Given that a field default value initialization can benefit, as follows:
<code php>
$ross = Employee {
firstName = "Ross",
lastName = "Bing",
};
</code>
Above example shows the initialization of struct named `Employee` with some default values used to initialize `$salary` and `$fullTime` fields as they're declared in the previous section.
<blockquote>
**Note!** Initialization of default value for `$salary` field of type `Salary` was used when declaring `Employee` struct.
This is possible only when struct fields consist only with scalar types (expressions are not allowed cause they can infer global state).
</blockquote>
<code php>
struct Point {
int $x = 0, $y = 0;
}
$point = Point;
$point->x = 10;
$point->y = 20;
</code>
Initializing default values for struct fields allows us to initialize struct with default initialization and initializing fields in separated statements what is presented in the above example.
In some cases like a named struct `Point` where it becomes quite intuitive that there are `x` and `y` fields initialization can be reduced to passing only values in a comma-separated list as shown below:
<code php>
$point = Point { 10, 20};
</code>
----
Some programming languages have custom struct initializers.
In *PHP* this would conflict with functions syntax cause usually custom initializers are functions and therefore they use parentheses with a list of function arguments.
A solution for that may be the use of function with the same name as struct alias to declare such initializer outside of struct declaration.
Given that it is possible to define a function, as follows:
<code php>
function Employee(
string $firstName,
string $lastName,
Salary $salary = Salary { salary = 1200, insurance = 0, allowance = 0 }
): Employee {
return Employee {
firstName = $firstName,
lastName = $lastName,
salary = $salary,
};
}
$ross = Employee("Ross", "Bing");
</code>
To satisfy all kind of needs like custom initialization using different input types we can go with a factory class, as follows:
<code php>
use Symfony\Component\HttpFoundation\Request;
class EmployeeFactory {
public function fromRequest(Request $request): Employee {
return Employee {
firstName = $request->request->get("first_name"),
lastName = $request->request->get("last_name"),
salary = Salary {
salary = $request->request->getInt("salary"),
insurance = $request->request->getInt("insurance"),
allowance = $request->request->getInt("allowance"),
},
fullTime = $request->request->getBoolean("full_time"),
};
}
public function fromArray(array $data): Employee {
return Employee {
firstName = $data["first_name"],
lastName = $data["last_name"],
salary = Salary {
salary = (int) $data["salary"],
insurance = (int) $data["insurance"],
allowance = (int) $data["allowance"],
},
fullTime = (bool) $data["full_time"],
};
}
}
$factory = new EmployeeFactory();
$request = Request::createFromGlobals();
$employee = $factory->fromRequest($request);
$john = $factory->fromArray([
"first_name" => "John",
"last_name" => "Doe",
"salary" => 1000,
"insurance" => 100,
"allowance" => 100,
"full_time" => true,
]);
</code>
<blockquote>
**Note!** In the above example factory class has a different name than the struct itself - this differs from the previous example with a function named `Employee` because a class is a user-defined type and functions are not types. Naming class with the same name would collide in symbol resolution.
</blockquote>
This leads to one small implication. When types are defined inside a namespace and we want to use short names we need to add direct `use` clauses.
<code php>
namespace Accounting {
struct Salary {
int $salary = 1000, $insurance = 50, $allowance = 50;
}
struct Employee {
string $firstName, $lastName;
Salary $salary = Salary { salary = 1200, insurance = 0, allowance = 0 };
bool $fullTime = true;
}
function Employee(
string $firstName,
string $lastName,
Salary $salary = Salary { salary = 1200, insurance = 0, allowance = 0 }
): Employee {
return Employee {
firstName = $firstName,
lastName = $lastName,
salary = $salary,
};
}
}
namespace App {
use struct Accounting\Salary;
use function Accounting\Employee;
$ross = Employee("Ross", "Bing", Salary { 1200, 100, 100 });
}
</code>
Above example show the use of function name `Employee` from another namespace and the best what we can see in all above examples *PHP* gives us plenty of ways allowing to implement custom initialization mechanisms without the need to add any logic onto structs.
==== Anonymous structs ====
In some cases, we don't need a struct to be named and all we need is it to present a kind of tuple with fields named and specific types guarded. This can be solved by introducing anonymous structs.
Internally the struct definition is created in a similar way to anonymous classes, they do have a generated name but that is simply not exposed to the userland.
<code php>
$monica = struct {
string firstName = "Monica",
string lastName = "Gellard",
int salary = 1200,
bool fullTime = true,
};
</code>
==== Fields composition ====
An interesting feature of the struct is the inclusion of the fields allowing to compose complex structs with the use of already existing structs definition:
<code php>
struct Salary {
int $salary = 1000, $insurance = 50, $allowance = 50;
}
struct Person {
string $firstName, $lastName;
}
struct Employee {
Person;
Salary;
}
$ross = Employee {
firstName: "Ross"
lastName: "Bing"
salary = 1200,
insurance = 0,
allowance = 0,
}
println(
"%s's basic salary is %d, insurance is %d and allowance is %d",
$ross->firstName,
$ross->salary,
$ross->insurance,
$ross->allowance
);
</code>
In the above example of the nested struct, we removed `salary` field name and just used `Salary` struct type to include fields from it into `Employee` the same goes for including of fields from `Person`.
===== Backward Incompatible Changes =====
No BC breaks.
===== Proposed PHP Version(s) =====
Next PHP 8.x.
===== RFC Impact =====
==== To SAPIs ====
None
==== To Existing Extensions ====
No
==== To Opcache ====
Should be verified
==== New Constants ====
None
===== Future Scope =====
==== Casting ====
Casting structs can be a future enhancement of structs proposal.
Due to all struct fields being visible and their type exposed it would be possible to add "down-casting" to any kind of struct if both field names and types matches, like in example below:
<code php>
struct Salary {
int $salary = 1000, $insurance = 50, $allowance = 50;
}
struct Person {
string $firstName, $lastName;
}
struct Employee {
Person;
Salary;
}
$ross = Employee {
firstName: "Ross"
lastName: "Bing"
salary = 1200,
insurance = 0,
allowance = 0,
}
$person = (Person) $ross;
println(
"Person %s %s",
$person->firstName,
$person->lastName
);
</code>
===== Proposed Voting Choices =====
The primary vote requires 2/3.
===== Patches and Tests =====
TBD.
===== Implementation =====
TBD.
===== References =====
External references:
* [[https://blog.usejournal.com/struct-vs-classes-5a269960a2f6|Struct vs Classes]]
* [[https://dlang.org/spec/struct.html|Structs, Unions in D]]
* [[https://medium.com/rungo/structures-in-go-76377cc106a2|Structures in Go (structs)]]
* [[https://en.wikipedia.org/wiki/Struct_(C_programming_language)|struct (C programming language)]]
* [[https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/using-structs|Using Structs (C# Programming Guide)]]
* [[https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/structs|Structs (C# Programming Guide)]]
<?php
struct Salary {
int $salary = 1000, $insurance = 50, $allowance = 50;
} // fields definition with the same type can be groupped
struct Person {
string $firstName, $lastName;
} // fields doesn't require default values
struct Employee {
Person; // fields can be inlined from another struct
Salary $salary = Salary { salary = 1200, insurance = 0, allowance = 0 };
bool $fullTime = true;
}
function Employee(
string $firstName,
string $lastName,
Salary $salary = Salary { salary = 1200, insurance = 0, allowance = 0 }
): Employee {
return Employee {
salary = $salary,
firstName = $firstName,
lastName = $lastName,
}; // a struct can be initializer with field name-value pairs in different order
}
$ross = Employee("Ross", "Bing"); // function with the same name can be used as a specialized initializer
@Sarke
Copy link

Sarke commented Mar 19, 2021

Hi @brzuchal, can I ask if this will move forward? I don't see this RFC ever discussed on internals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment