Key Takeaways
- PHP 5.6 introduced the ability to create typed arrays using the … token, which denotes that a function or method accepts a variable length of arguments. This feature can be combined with type hints to ensure that only certain types of objects are accepted in an array.
- One limitation of this feature is that only one typed array can be defined per method. To overcome this, typed arrays can be injected into “collection” classes, which also allows for more specific return types than “array” on get methods.
- Value objects can be used for custom validation. For instance, a Rating value object could be created with constraints to ensure that a rating is always between 0 and 5. This provides additional validation of individual collection members without having to loop over each injected object.
- Strictly typed arrays and collections have several advantages. They provide easy type validation in one place, ensure values have always been validated upon construction, allow for the addition of custom logic per collection, and reduce the odds of mixing up arguments in method signatures.
- While it’s possible to add methods to facilitate edits to the values of collections and value objects after initial construction, it’s more efficient to keep them immutable and convert them to their primitive types when changes need to be made. After making changes, the collections or value objects can be reconstructed with the updated values, which will then be validated again.
This post first appeared on Medium and was republished here with the author’s permission. We encourage you to follow Bert on Medium and give him some likes there!
One of the language features announced back in PHP 5.6 was the addition of the ... token to denote that a function or method accepts a variable length of arguments.
Something I rarely see mentioned is that it’s possible to combine this feature with type hints to essentially create typed arrays.
For example, we could have a Movie class with a method to set an array of air dates that only accepts DateTimeImmutable objects:
<span><span><?php </span></span><span> </span><span><span>class Movie { </span></span><span> <span>private $dates = []; </span></span><span> </span><span> <span>public function setAirDates(\DateTimeImmutable ...$dates) { </span></span><span> <span>$this->dates = $dates; </span></span><span> <span>} </span></span><span> </span><span> <span>public function getAirDates() { </span></span><span> <span>return $this->dates; </span></span><span> <span>} </span></span><span><span>} </span></span></span>
We can now pass a variable number of separate DateTimeImmutable objects to the setAirDates() method:
<span><span><?php </span></span><span> </span><span><span>$movie = new Movie(); </span></span><span> </span><span><span>$movie->setAirDates( </span></span><span> <span><span>\DateTimeImmutable</span>::createFromFormat('Y-m-d', '2017-01-28'), </span></span><span> <span><span>\DateTimeImmutable</span>::createFromFormat('Y-m-d', '2017-02-22') </span></span><span><span>); </span></span></span>
If we were to pass something else than a DateTimeImmutable, a string for example, a fatal error would be thrown:
If we instead already had an array of DateTimeImmutable objects that we wanted to pass to setAirDates(), we could again use the ... token, but this time to unpack them:
<span><span><?php </span></span><span> </span><span><span>$dates = [ </span></span><span> <span><span>\DateTimeImmutable</span>::createFromFormat('Y-m-d', '2017-01-28'), </span></span><span> <span><span>\DateTimeImmutable</span>::createFromFormat('Y-m-d', '2017-02-22'), </span></span><span><span>]; </span></span><span> </span><span><span>$movie = new Movie(); </span></span><span><span>$movie->setAirDates(...$dates); </span></span></span>
If the array were to contain a value that is not of the expected type, we would still get the fatal error mentioned earlier.
Additionally, we can use scalar types the same way starting from PHP 7. For example, we can add a method to set a list of ratings as floats on our Movie class:
<span><span><?php </span></span><span> </span><span><span>class Movie { </span></span><span> <span>private $dates = []; </span></span><span> </span><span> <span>public function setAirDates(\DateTimeImmutable ...$dates) { </span></span><span> <span>$this->dates = $dates; </span></span><span> <span>} </span></span><span> </span><span> <span>public function getAirDates() { </span></span><span> <span>return $this->dates; </span></span><span> <span>} </span></span><span><span>} </span></span></span>
Again, this ensures that the ratings property will always contain floats without us having to loop over all the contents to validate them. So now we can easily do some math operations on them in getAverageRating(), without having to worry about invalid types.
Problems with This Kind of Typed Arrays
One of the downsides of using this feature as typed arrays is that we can only define one such array per method. Let’s say we wanted to have a Movie class that expects a list of air dates together with a list of ratings in the constructor, instead of setting them later via optional methods. This would be impossible with the method used above.
Another problem is that when using PHP 7, the return types of our get() methods would still have to be “array”, which is often too generic.
Solution: Collection Classes
To fix both problems, we can simply inject our typed arrays inside so-called “collection” classes. This also improves our separation of concerns, because we can now move the calculation method for the average rating to the relevant collection class:
<span><span><?php </span></span><span> </span><span><span>$movie = new Movie(); </span></span><span> </span><span><span>$movie->setAirDates( </span></span><span> <span><span>\DateTimeImmutable</span>::createFromFormat('Y-m-d', '2017-01-28'), </span></span><span> <span><span>\DateTimeImmutable</span>::createFromFormat('Y-m-d', '2017-02-22') </span></span><span><span>); </span></span></span>
Notice how we’re still using a list of typed arguments with a variable length in our constructor, which saves us the trouble of looping over each rating to check its type.
If we wanted the ability to use this collection class in foreach loops, we’d simply have to implement the IteratorAggregate interface:
<span><span><?php </span></span><span> </span><span><span>$dates = [ </span></span><span> <span><span>\DateTimeImmutable</span>::createFromFormat('Y-m-d', '2017-01-28'), </span></span><span> <span><span>\DateTimeImmutable</span>::createFromFormat('Y-m-d', '2017-02-22'), </span></span><span><span>]; </span></span><span> </span><span><span>$movie = new Movie(); </span></span><span><span>$movie->setAirDates(...$dates); </span></span></span>
Moving on, we can also create a collection for our list of air dates:
<span><span><?php </span></span><span> </span><span><span>declare(strict_types=1); </span></span><span> </span><span><span>class Movie { </span></span><span> <span>private $dates = []; </span></span><span> <span>private $ratings = []; </span></span><span> </span><span> <span>public function setAirDates(\DateTimeImmutable ...$dates) { /* ... */ } </span></span><span> <span>public function getAirDates() : array { /* ... */ } </span></span><span> </span><span> <span>public function setRatings(float ...$ratings) { </span></span><span> <span>$this->ratings = $ratings; </span></span><span> <span>} </span></span><span> </span><span> <span>public function getAverageRating() : float { </span></span><span> <span>if (empty($this->ratings)) { </span></span><span> <span>return 0; </span></span><span> <span>} </span></span><span> </span><span> <span>$total = 0; </span></span><span> </span><span> <span>foreach ($this->ratings as $rating) { </span></span><span> <span>$total += $rating; </span></span><span> <span>} </span></span><span> </span><span> <span>return $total / count($this->ratings); </span></span><span> <span>} </span></span><span><span>} </span></span></span>
Putting all the pieces of the puzzle together in the Movie class, we can now inject two separately typed collections in our constructor. Additionally we can define more specific return types than “array” on our get methods:
<span><span><?php </span></span><span> </span><span><span>declare(strict_types=1); </span></span><span> </span><span><span>class Ratings { </span></span><span> <span>private $ratings; </span></span><span> </span><span> <span>public function __construct(float ...$ratings) { </span></span><span> <span>$this->ratings = $ratings; </span></span><span> <span>} </span></span><span> </span><span> <span>public function getAverage() : float { </span></span><span> <span>if (empty($this->ratings)) { </span></span><span> <span>return 0; </span></span><span> <span>} </span></span><span> </span><span> <span>$total = 0; </span></span><span> </span><span> <span>foreach ($this->ratings as $rating) { </span></span><span> <span>$total += $rating; </span></span><span> <span>} </span></span><span> </span><span> <span>return $total / count($this->ratings); </span></span><span> <span>} </span></span><span><span>} </span></span></span>
Using Value Objects for Custom Validation
If we wanted to add extra validation to our ratings we could still go one step further, and define a Rating value object with some custom constraints. For example, a rating could be limited between 0 and 5:
<span><span><?php </span></span><span> </span><span><span>declare(strict_types=1); </span></span><span> </span><span><span>class Ratings implements IteratorAggregate { </span></span><span> <span>private $ratings; </span></span><span> </span><span> <span>public function __construct(float ...$ratings) { </span></span><span> <span>$this->ratings = $ratings; </span></span><span> <span>} </span></span><span> </span><span> <span>public function getAverage() : float { /* ... */ } </span></span><span> </span><span> <span>public function getIterator() { </span></span><span> <span>return new ArrayIterator($this->ratings); </span></span><span> <span>} </span></span><span><span>} </span></span></span>
Back in our Ratings collection class, we would only have to do some minor alterations to use these value objects instead of floats:
<span><span><?php </span></span><span> </span><span><span>class AirDates implements IteratorAggregate { </span></span><span> <span>private $dates; </span></span><span> </span><span> <span>public function __construct(\DateTimeImmutable ...$dates) { </span></span><span> <span>$this->dates = $dates; </span></span><span> <span>} </span></span><span> </span><span> <span>public function getIterator() { </span></span><span> <span>return new ArrayIterator($this->airdates); </span></span><span> <span>} </span></span><span><span>} </span></span></span>
This way we get additional validation of individual collection members, still without having to loop over each injected object.
Advantages
Typing out these separate collection classes and value object may seem like a lot of work, but they have several advantages over generic arrays and scalar values:
Easy type validation in one place. We never have to manually loop over an array to validate the types of our collection members;
Wherever we use these collections and value objects in our application, we know that their values have always been validated upon construction. For example, any Rating will always be between 0 and 5;
We can easily add custom logic per collection and/or value object. For example the getAverage() method, which we can re-use throughout our whole application;
We get the possibility to inject multiple typed lists in a single function or method, which we cannot do using the ... token without injecting the values in collection classes first;
There are significantly reduced odds of mixing up arguments in method signatures. For example, when we want to inject both a list of ratings and a list of air dates, the two could easily get mixed up by accident upon construction when using generic arrays;
What about edits?
By now you might be wondering how you could make changes to the values of your collections and value objects after initial construction.
While we could add methods to facilitate edits, this would quickly become cumbersome because we would have to duplicate most methods on each collection to keep the advantage of type hints. For example, an add() method on Ratings should only accept a Rating object, while an add() method on AirDates should only accept a DateTimeImmutable object. This makes interfacing and/or re-use of these methods very hard.
Instead, we could simply keep our collections and value objects immutable, and convert them to their primitive types when we need to make changes. After we’re done making changes, we can simple re-construct any necessary collections or value objects with the updated values. Upon (re-)construction all types would be validated again, along with any extra validation we might have defined.
For example, we could add a simple toArray() method to our collections, and make changes like this:
<span><span><?php </span></span><span> </span><span><span>class Movie { </span></span><span> <span>private $dates = []; </span></span><span> </span><span> <span>public function setAirDates(\DateTimeImmutable ...$dates) { </span></span><span> <span>$this->dates = $dates; </span></span><span> <span>} </span></span><span> </span><span> <span>public function getAirDates() { </span></span><span> <span>return $this->dates; </span></span><span> <span>} </span></span><span><span>} </span></span></span>
This way we can also re-use existing array functionality like array_filter().
If we really needed to do edits on the collection objects themselves, we could add the necessary methods on a need-to-have basis wherever they are required. But keep in mind that most of those will also have to do type validation of the given argument(s), so it’s hard to re-use them across all different collection classes.
Re-Using Generic Methods
As you may have noticed we are still getting some code duplication across our collection classes by implementing both toArray() and getIterator() on all of them. Luckily these methods are generic enough to move to a generic parent class, as they both simply return the injected array:
<span><span><?php </span></span><span> </span><span><span>$movie = new Movie(); </span></span><span> </span><span><span>$movie->setAirDates( </span></span><span> <span><span>\DateTimeImmutable</span>::createFromFormat('Y-m-d', '2017-01-28'), </span></span><span> <span><span>\DateTimeImmutable</span>::createFromFormat('Y-m-d', '2017-02-22') </span></span><span><span>); </span></span></span>
All we would be left with in our collection class would be type validation in the constructor, and any optional extra logic that is specific to that collection, like this:
<span><span><?php </span></span><span> </span><span><span>class Movie { </span></span><span> <span>private $dates = []; </span></span><span> </span><span> <span>public function setAirDates(\DateTimeImmutable ...$dates) { </span></span><span> <span>$this->dates = $dates; </span></span><span> <span>} </span></span><span> </span><span> <span>public function getAirDates() { </span></span><span> <span>return $this->dates; </span></span><span> <span>} </span></span><span><span>} </span></span></span>
Optionally we could make our collection final, to prevent any child classes from messing with the values property in ways that could undo our type validation.
Conclusion
While still far from perfect, it has steadily been getting easier to work with type validation in collections and value objects with recent releases of PHP.
Ideally we’d get some form of generics in a future version of PHP to further facilitate the creation of re-usable collection classes.
A feature that would greatly improve the usage of value objects would be the ability to cast an object to different primitive types, in addition to string. This could easily be implemented by adding extra magic methods comparable to __toString(), like __toInt(), __toFloat(), etc.
Luckily there are some RFCs in progress to possibly implement both features in later versions, so fingers crossed! ?
Generics: https://wiki.php.net/rfc/generics
Generic arrays: https://wiki.php.net/rfc/generic-arrays
Casting object to scalar: https://wiki.php.net/rfc/class_casting_to_scalar
If you found this tutorial helpful, please visit the original post on Medium and give it some ❤️. If you have any feedback, questions, or comments, please leave them below or as a response on the original post.
Frequently Asked Questions (FAQs) about Creating Strictly Typed Arrays and Collections in PHP
What are the benefits of using strictly typed arrays in PHP?
Strictly typed arrays in PHP provide a way to ensure that all elements in an array are of a specific type. This can be particularly useful in larger, more complex applications where data consistency is crucial. By enforcing a specific type for all elements in an array, you can prevent potential bugs and errors that might occur due to unexpected data types. It also makes your code more predictable and easier to debug, as you always know the type of data you’re working with.
How can I create a strictly typed array in PHP?
PHP does not natively support strictly typed arrays. However, you can create a class that enforces type checking on the elements added to the array. This class would have methods for adding and retrieving elements, and these methods would check the type of the element before performing the operation. If the type of the element does not match the expected type, an error would be thrown.
Can I use type hinting with arrays in PHP?
Yes, PHP supports type hinting for arrays. You can specify that a function or method expects an array as an argument by adding “array” before the argument name in the function or method declaration. However, this only ensures that the argument is an array, not that all elements in the array are of a specific type.
What is the difference between loosely typed and strictly typed arrays?
In a loosely typed array, the elements can be of any type. In a strictly typed array, all elements must be of a specific type. If you try to add an element of a different type to a strictly typed array, an error will be thrown.
How can I enforce type checking in PHP?
You can enforce type checking in PHP by using the “declare(strict_types=1);” directive at the beginning of your PHP file. This will enforce strict type checking for all function calls and return statements in the file.
Can I create a strictly typed array of objects in PHP?
Yes, you can create a strictly typed array of objects in PHP by creating a class that enforces type checking on the objects added to the array. The class would have methods for adding and retrieving objects, and these methods would check the type of the object before performing the operation.
What are the limitations of strictly typed arrays in PHP?
The main limitation of strictly typed arrays in PHP is that they require additional code to implement, as PHP does not natively support them. This can make your code more complex and harder to maintain. Additionally, strictly typed arrays can be less flexible than loosely typed arrays, as they do not allow for elements of different types.
Can I use type hinting with multidimensional arrays in PHP?
Yes, you can use type hinting with multidimensional arrays in PHP. However, PHP’s type hinting only ensures that the argument is an array, not that all elements in the array (or sub-arrays) are of a specific type.
How can I handle errors when using strictly typed arrays in PHP?
When using strictly typed arrays in PHP, you can handle errors by using try-catch blocks. If an error occurs when adding an element to the array (for example, if the element is of the wrong type), an exception will be thrown. You can catch this exception and handle it appropriately.
Can I use strictly typed arrays with PHP’s built-in array functions?
Yes, you can use strictly typed arrays with PHP’s built-in array functions. However, you need to be careful, as these functions do not enforce type checking. If you use a function that modifies the array and adds an element of the wrong type, this could lead to errors.
The above is the detailed content of Creating Strictly Typed Arrays and Collections in PHP. For more information, please follow other related articles on the PHP Chinese website!

What’s still popular is the ease of use, flexibility and a strong ecosystem. 1) Ease of use and simple syntax make it the first choice for beginners. 2) Closely integrated with web development, excellent interaction with HTTP requests and database. 3) The huge ecosystem provides a wealth of tools and libraries. 4) Active community and open source nature adapts them to new needs and technology trends.

PHP and Python are both high-level programming languages that are widely used in web development, data processing and automation tasks. 1.PHP is often used to build dynamic websites and content management systems, while Python is often used to build web frameworks and data science. 2.PHP uses echo to output content, Python uses print. 3. Both support object-oriented programming, but the syntax and keywords are different. 4. PHP supports weak type conversion, while Python is more stringent. 5. PHP performance optimization includes using OPcache and asynchronous programming, while Python uses cProfile and asynchronous programming.

PHP is mainly procedural programming, but also supports object-oriented programming (OOP); Python supports a variety of paradigms, including OOP, functional and procedural programming. PHP is suitable for web development, and Python is suitable for a variety of applications such as data analysis and machine learning.

PHP originated in 1994 and was developed by RasmusLerdorf. It was originally used to track website visitors and gradually evolved into a server-side scripting language and was widely used in web development. Python was developed by Guidovan Rossum in the late 1980s and was first released in 1991. It emphasizes code readability and simplicity, and is suitable for scientific computing, data analysis and other fields.

PHP is suitable for web development and rapid prototyping, and Python is suitable for data science and machine learning. 1.PHP is used for dynamic web development, with simple syntax and suitable for rapid development. 2. Python has concise syntax, is suitable for multiple fields, and has a strong library ecosystem.

PHP remains important in the modernization process because it supports a large number of websites and applications and adapts to development needs through frameworks. 1.PHP7 improves performance and introduces new features. 2. Modern frameworks such as Laravel, Symfony and CodeIgniter simplify development and improve code quality. 3. Performance optimization and best practices further improve application efficiency.

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP type prompts to improve code quality and readability. 1) Scalar type tips: Since PHP7.0, basic data types are allowed to be specified in function parameters, such as int, float, etc. 2) Return type prompt: Ensure the consistency of the function return value type. 3) Union type prompt: Since PHP8.0, multiple types are allowed to be specified in function parameters or return values. 4) Nullable type prompt: Allows to include null values and handle functions that may return null values.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

SAP NetWeaver Server Adapter for Eclipse
Integrate Eclipse with SAP NetWeaver application server.

ZendStudio 13.5.1 Mac
Powerful PHP integrated development environment

WebStorm Mac version
Useful JavaScript development tools

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),

MinGW - Minimalist GNU for Windows
This project is in the process of being migrated to osdn.net/projects/mingw, you can continue to follow us there. MinGW: A native Windows port of the GNU Compiler Collection (GCC), freely distributable import libraries and header files for building native Windows applications; includes extensions to the MSVC runtime to support C99 functionality. All MinGW software can run on 64-bit Windows platforms.