Guidelines for upgrading the minimum PHP version requirements of packages and projects.
This isn't meant to be an exhaustive guide to upgrading, but as a checklist for the most important upgrades.
The first version to support namespaces - any relevant PHP packages/projects usually have this version as the minimum requirement, so this document won't concern itself with upgrades prior to that.
- Short array syntax: replace
array(...)
with[...]
- Traits: probably don't use this feature, unless maybe you're already doing dodgy run-time hocus-pocus to achieve something similar - but even then, consider refactoring to composition.
- Closures now support
$this
in classes: remove e.g. redundant$that = $this
andfunction () use ($that)
declarations in favor of$this
. - Replace
<?php echo
with<?=
in templates: it's now always available, regardless ofshort_open_tag
inphp.ini
. - Use the upload progress feature instead of other solutions to achieve the same.
- Consider refactoring to generators, if applicable.
- Refactor to finally in
try-catch
blocks, if applicable. - Use native password hashing: avoid insecure
sha1
with fixed salt, avoid custom libraries, etc. (Avoid using custom salt, which was deprecated in PHP 7.0.) - Use
::class
constants: avoid string literals when specifying class-names.
- Use variable-length argument lists: avoid using
func_get_args()
etc. where possible. - Import functions and constants: avoid fully-qualified references to namespaced functions and constants.
- Use scalar type-hints
int
,string
,float
andbool
, where applicable. - Add return-type declarations to functions and methods. (If targeting PHP 7.1, add
void
return-type declarations to functions/methods that return nothing.) - Use the
??
null-coalescing operator where applicable. - Consider using anonymous classes: mainly for things like mocks in unit tests.
- Consider grouping use-statements: mainly useful with
use function
statements to import a group of functions. (PSR-2 requires individualuse
statements per declaration - it does not consider imports of functions/constants, which did not exist when this was standardized. PSR-12 does not require individual import statements.) - Generator return-expressions: adds confusing generator/function-hybrids, probably don't use.
- Generator delegation via
yield from
: replace verboseforeach ($x as $y => $z) { yield $y => $z; }
with better performingyield from $x
. - Avoid PHP 4 style constructors: always use
__construct
. - Static calls to methods not explicitly declared as
static
: addstatic
where needed. - Consider using cryptographically secure random numbers where applicable.
- If using reflection, consider using new reflection methods to obtain parameter-types and return-types.
- Improve any error-handlers to make use of Throwable and Error classes, where applicable.
- Long-deprecated
mysql
,mssql
andereg
extensions have been removed: must use PDO andpreg_*
functions now.
- Add
void
return-type declarations to functions/methods that return nothing. - Use nullable types where applicable: avoid nullable parameters when defaulting to
null
is undesirable, e.g.function (Foo $foo = null)
should befunction (?Foo $foo)
when the parameter is non-optional. - Add iterable type-hint where applicable: essentially,
iterable
is equivalent to thearray|Iterator|Traversable
type union, which you were unable to properly type-hint in previous PHP versions. - Add visibility modifiers to class constants: at least
private
andprotected
, where meaningful. (PSR-12 requires explicit visibility modififers on all constant declarations.) - Use multiple catch where applicable.
- Use Closure::fromCallable where applicable: add proper
Closure
type-hints where possible - avoid using untyped or pseudotypedcallable
parameter/return-values.
- Use
object
type-hint where previously no type-hint was applicable. - Use variance, where applicable: you may have been forced to omit type-hints in base-classes or interfaces. (Note: this works only for parameter types - support for return-types arrived in PHP 7.4.)
Nothing to see here, move along.
- Add property type-hints where applicable. Probably don't replace getter/setter methods with type-hinted properties, which support neither abstraction nor validation.
- Arrow functions: if you must, use this for very simple inline functions only. (Considerations: syntax inconsistently uses the
fn
(fnuh) keyword; syntax is ambiguous with array syntax; using a statement requires refactoring to regular inlinefunction
syntax.) - Use variance for return-types, where applicable: you may have been forced to omit type-hints in base-classes or interfaces.
- Use array unpacking where applicable:
$all_items = [$first, $second, ...$more_items, $last]
- Specify username/password for PDO connections via DSN string: separate arguments for username and password are no longer required.
- Avoid
Serializable
interface: use magic methods__serialize
and__unserialize
instead. (The interface will be deprecated in the future.) - Nested ternaries must now use parenthesis to explicitly indicate evaluation order.
- Avoid curly braces when accessing array or string elements: use square brackets, e.g.
$var[$x]
rather than$var{$x}
.
- Use named arguments where applicable: constructors with many arguments, the typical
array $options
parameter often used in lieu of named arguments, and so on. - Use native attributes where applicable: replace third-party annotation libraries. Consider using this where other patters are used to establish meta-data of any sort - any library where property- or method-names are specified as strings are likely candidates for refactoring to native attributes.
- Constructor property promotion: probably don't use this except for pure data-structures, where you can be certain no validation (beyond type-hints) will ever be required - or you will likely end up refactoring back and forth between this and regular properties.
- Add union types where previously these were untyped or annotated with doc-blocks.
- Consider refactoring from
switch
statements tomatch
expressions, where the outcome is a value or expression, as opposed to an action/statement. (Note thatmatch
uses strict comparison, whereasswitch
uses loose comparison.) - Consider using null-safe property/method access: note that this is applicable to branches with strict null comparisons and no exceptions only.
- Consider using the
mixed
type-hint: mostly useful for return-types - where thevoid
return-type means "returns nothing", this essentially means the opposite, explicitly indicating that a function "returns something". (Contrast this with having no return-type, which could mean either "returns something" or "returns nothing".)
- Consider using enums, where previously you might have been using
public const
members. The motivation to use enums is type-safety - if you decide to use enums instead, be sure to add type-hints where applicable. - Consider using
readonly
properties, which can replaceprivate
fields with boilerplate getter-methods. Note that this is a breaking change, which can lead to more breaking changes if, for any reason, at a later time, the returned value needs to change to a computed value - and, for the same reason, this will introduce some inconsistencies in your codebase. (This advice would change if PHP introduces property getter/setters in the future.) - Consider migrating from
Closure::fromCallable
or[$object, "method"]
callables to first-class callables, e.g.$object->method(...)
. Note that this feature does not allow partial applications - to create partial applications, use e.g.fn($b) => $object->method("a", $b)
instead. - Consider using
new
in initializers - as opposed to manually implementing default initializations. This removes the need to usenull
as a default wherenull
is not actually valid. - Nested attributes are now possible. Please try not to go overboard.
- Consider using intersection types, mostly to combine interface types such as
Foo&Bar
, where previously, type-hinting may not have been possible, or you may have been forced to add a meaningless interface, such asFooAndBar
. Note that, while union types exist (and were added in PHP 8.0) these may not be combined (e.g.A&B|C
) with intersection types in PHP 8.1 - this feature was added in PHP 8.2. - The
never
return-type was added, perhaps for that incredibly rare use-case where a daemon runs forever. (You will probably never use this.) final const
is now a thing. (I hope you've never actually overridden a constant.)- Fibers are a really exciting new feature for things like background task-runners, message queues, or other daemons.
- Merging arrays with string keys now works, e.g.
[...$A, ...$B]
rather thanarray_merge($A, $B)
. - Classes that implement
Serializable
must now actually implement the__serialize
and__unserialize
methods. Look for__sleep
and__wakeup
implementations and get rid of them. - MySQLi now defaults to throwing exceptions - if you hard coded your DB access using error codes, you should fix that. (or explicitly set the error mode, leave a TODO, come back and fix it later.)
readonly class
is now a thing - pretty sweet for services/repositories with immutable data models.- Union and intersection types may now be combined, e.g.
(A&B)|C
- note that parens in groupings are required. null
,false
andtrue
are now types, if you need them. (recall thatvoid
is already a return-type since PHP 7, so don't start usingnull
in place of that.)- An enormous framework for generating random numbers, why not.
const
is now allowed intrait
- for those who insist on using traits for some reason.- Dynamic properties have been deprecated! This will cause lots of breakage in really old PHP scripts, and will hopefully break a few classes where you forgot to declare a property. This has no other impact on your code, but this is a really good thing, and the RFC is worth reading - hopefully this paves the way for getter/setters in the future, since two prior proposals (this one and that one) were both dead in the water (in part) because of this.
- String interpolation syntax
Hello ${world}
has been deprecated - search your codebase for${
and replace with{$
and you should be all good. utf8_encode
andutf8_decode
have been deprecated - usemb_*
functions instead.strtoupper
andstrtolower
no longer support current locale - this is a breaking change - usemb_strtoupper
andmb_strtolower
instead.
New features and migration guide are available, docs are still lagging behind...