> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# PHP 8.0 feature focus: quality of life improvements

> Explore PHP 8.0's new string functions, throw expression, and other improvements. Try PHP 8.0 on Platform.sh today!

export const PostMeta = ({data = {}}) => {
  const {author, date} = data;
  const authors = Array.isArray(author) ? author : author ? [author] : [];
  const resolveAuthor = slug => {
    const entry = AUTHOR_MAP[slug] || ({});
    const name = entry.name || slug;
    const github = entry.github || null;
    const linkedin = entry.linkedin || null;
    const url = github ? `https://github.com/${github}` : linkedin || null;
    const avatarUrl = github ? `https://github.com/${github}.png?size=64` : null;
    return {
      name,
      url,
      avatarUrl
    };
  };
  const formattedDate = date ? new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }) : null;
  if (authors.length === 0 && !formattedDate) return null;
  const AUTHOR_MAP = {
    "aaron-collier": {
      "name": "Aaron Collier"
    },
    "aaron-dudenhofer": {
      "name": "Aaron Dudenhofer"
    },
    "aaron-porter": {
      "name": "Aaron Porter"
    },
    "adriaan-odendaal": {
      "name": "Adriaan Odendaal"
    },
    "ajmal": {
      "name": "Ajmal Siddiqui"
    },
    "akalipetis": {
      "name": "Antonis Kalipetis"
    },
    "alexander-varwijk": {
      "name": "Alexander Varwijk"
    },
    "alicia-bevilacqua": {
      "name": "Alicia Bevilacqua"
    },
    "amelie-deguerry": {
      "name": "Amelie Deguerry"
    },
    "anacidre": {
      "name": "Ana Cidre",
      "linkedin": "https://www.linkedin.com/in/ana-cidre"
    },
    "andoni": {
      "name": "Andoni Auzmendi"
    },
    "andrei-taranu": {
      "name": "Andrei (Alex) Taranu",
      "linkedin": "https://www.linkedin.com/in/andrei-alex-taranu/"
    },
    "andrew-baxter": {
      "name": "Andrew Baxter"
    },
    "andrew-melck": {
      "name": "Andrew Melck"
    },
    "antoine-crochet-damais": {
      "name": "Antoine Crochet Damais"
    },
    "augustin-delaporte": {
      "name": "Augustin Delaporte",
      "linkedin": "https://www.linkedin.com/in/augustindelaporte/"
    },
    "branislav-bujisic": {
      "name": "Branislav Bujisic"
    },
    "carl-smith": {
      "name": "Carl Smith"
    },
    "caroline-leroy": {
      "name": "Caroline Leroy"
    },
    "cati-mayer": {
      "name": "Cati Mayer"
    },
    "catplat": {
      "name": "C Trinkwon"
    },
    "ceelolulu": {
      "name": "Celeste van der Watt"
    },
    "chadwcarlson": {
      "name": "Chad Carlson",
      "github": "chadwcarlson",
      "linkedin": "https://www.linkedin.com/in/chadwcarlson"
    },
    "chris-ward": {
      "name": "Chris Ward"
    },
    "chris-yates": {
      "name": "Chris Yates"
    },
    "christian-sieber": {
      "name": "Christian Sieber"
    },
    "christopher-lockheardt": {
      "name": "Christopher Lockheardt"
    },
    "christopher-skene": {
      "name": "Christopher Skene"
    },
    "chuck-morgan": {
      "name": "Chuck Morgan"
    },
    "corey-dockendorf": {
      "name": "Corey Dockendorf"
    },
    "crell": {
      "name": "Crell"
    },
    "damz": {
      "name": "Damz"
    },
    "dan-morrison": {
      "name": "Dan Morrison"
    },
    "davidbonachera": {
      "name": "David Bonachera",
      "github": "davidbonachera",
      "linkedin": "https://www.linkedin.com/in/davidbonachera"
    },
    "dereliahmet1": {
      "name": "Ahmet Faruk Dereli"
    },
    "devicezero": {
      "name": "Jonas Kröger",
      "github": "devicezero",
      "linkedin": "https://www.linkedin.com/in/jonaskroeger/"
    },
    "doug-goldberg": {
      "name": "Doug Goldberg"
    },
    "duncan-naves": {
      "name": "Duncan Naves",
      "github": "duncannaves",
      "linkedin": "https://www.linkedin.com/in/duncan-naves-a94423aa"
    },
    "erika-bustamante": {
      "name": "Erika Bustamante"
    },
    "fabpot": {
      "name": "Fabien Potencier"
    },
    "flovntp": {
      "name": "Florent Huck",
      "github": "flovntp",
      "linkedin": "https://www.linkedin.com/in/florenthuck"
    },
    "fred-plais": {
      "name": "Fred Plais"
    },
    "gauthier-garnier": {
      "name": "Gauthier Garnier"
    },
    "gilzow": {
      "name": "Paul Gilzow"
    },
    "gmoigneu": {
      "name": "Guillaume Moigneu",
      "github": "gmoigneu",
      "linkedin": "https://www.linkedin.com/in/guillaumemoigneu/"
    },
    "gregqualls": {
      "name": "Greg Qualls"
    },
    "guguss": {
      "name": "Augustin Delaporte"
    },
    "haylee-millar": {
      "name": "Haylee Millar"
    },
    "ivana-kotur": {
      "name": "Ivana Kotur"
    },
    "jackrabbithanna": {
      "name": "Mark Hanna"
    },
    "jared-wright": {
      "name": "Jared Wright",
      "github": "jww-sh",
      "linkedin": "https://www.linkedin.com/in/jaredwaynewright"
    },
    "jessica-orozco": {
      "name": "Jessica Orozco"
    },
    "joey-stanford": {
      "name": "Joey Stanford"
    },
    "john-grubb": {
      "name": "John Grubb"
    },
    "jonas-kruger": {
      "name": "Jonas Kruger"
    },
    "kathryn-frazer": {
      "name": "Kathryn Frazer"
    },
    "kemiojo": {
      "name": "Kemi Elizabeth Ojogbede"
    },
    "kieronsambrook-smith": {
      "name": "Kieronsambrook Smith"
    },
    "laurent-arnoud": {
      "name": "Laurent Arnoud",
      "linkedin": "https://www.linkedin.com/in/laurent-arnoud-861b44121/"
    },
    "letoya-boyne": {
      "name": "Letoya Boyne"
    },
    "lolautruche": {
      "name": "Jérôme Vieilledent"
    },
    "lyly-lepinay": {
      "name": "Lyly Lepinay"
    },
    "manauwar-alam": {
      "name": "Manauwar Alam"
    },
    "marc-antoine-porri": {
      "name": "Marc Antoine Porri"
    },
    "maria-antinkaapo": {
      "name": "Maria Antinkaapo"
    },
    "maria-de-anton": {
      "name": "Maria De Anton"
    },
    "mark-dorison": {
      "name": "Mark Dorison"
    },
    "markus-hausammann": {
      "name": "Markus Hausammann"
    },
    "mary-thomas": {
      "name": "Mary Thomas"
    },
    "mathias-bolt-lesniak": {
      "name": "Mathias Bolt Lesniak"
    },
    "mathieu-strauch": {
      "name": "Mathieu Strauch"
    },
    "matthias-van-woensel": {
      "name": "Matthias Van Woensel",
      "linkedin": "https://www.linkedin.com/in/matthias-van-woensel-267a069"
    },
    "maz-mohammadi": {
      "name": "Maz Mohammadi"
    },
    "michael-sharp": {
      "name": "Michael Sharp"
    },
    "mupsi": {
      "name": "Marine Gandy"
    },
    "natalie-harper": {
      "name": "Natalie Harper"
    },
    "ngommenginger": {
      "name": "Nicolas Gommenginger",
      "linkedin": "https://www.linkedin.com/in/nicolas-gommenginger"
    },
    "nicholas-bennison": {
      "name": "Nicholas Bennison"
    },
    "nicholas-vahalik": {
      "name": "Nicholas Vahalik"
    },
    "nick-hardiman": {
      "name": "Nick Hardiman"
    },
    "nickanderegg": {
      "name": "Nickanderegg"
    },
    "nicolas-grekas": {
      "name": "Nicolas Grekas",
      "github": "nicolas-grekas",
      "linkedin": "https://www.linkedin.com/in/nicolasgrekas/"
    },
    "niti-malwade": {
      "name": "Niti Malwade"
    },
    "opensocialteam": {
      "name": "Opensocialteam"
    },
    "ori-pekelman": {
      "name": "Ori Pekelman"
    },
    "otavio-santana": {
      "name": "Otavio Santana"
    },
    "palwandi": {
      "name": "Pawan Alwandi",
      "github": "pawpy",
      "linkedin": "https://www.linkedin.com/in/pawanalwandi"
    },
    "patrick-boest": {
      "name": "Patrick Boest"
    },
    "patrick-dawkins": {
      "name": "Patrick Dawkins",
      "github": "pjcdawkins",
      "linkedin": "https://www.linkedin.com/in/patrickdawkins"
    },
    "patrick-klima": {
      "name": "Patrick Klima"
    },
    "pjcdawkins": {
      "name": "Pjcdawkins"
    },
    "prineet-kaurbhurji": {
      "name": "Prineet Kaurbhurji"
    },
    "quentin-sinig": {
      "name": "Quentin Sinig"
    },
    "ralt": {
      "name": "Florian Margaine",
      "github": "ralt",
      "linkedin": "https://www.linkedin.com/in/florian-margaine-43971136"
    },
    "ramanathanramakrishnamurthy": {
      "name": "Ramanathanramakrishnamurthy"
    },
    "remi-lejeune": {
      "name": "Rémi Lejeune"
    },
    "ribel": {
      "name": "Taras Kruts"
    },
    "robert-douglass": {
      "name": "Robert Douglass"
    },
    "rudy-weber": {
      "name": "Rudy Weber"
    },
    "ryan-hicks": {
      "name": "Ryan Hicks"
    },
    "sabri-helal": {
      "name": "Sabri Helal"
    },
    "savannah-bergeron": {
      "name": "Savannah Bergeron"
    },
    "shannon-vettes": {
      "name": "Shannon Vettes"
    },
    "shawn-ogasawara": {
      "name": "Shawn Ogasawara",
      "linkedin": "https://www.linkedin.com/in/shawn-ogasawara-83a9a0/"
    },
    "shawna-spoor": {
      "name": "Shawna Spoor"
    },
    "shedrack-akintayo": {
      "name": "Shedrack Akintayo"
    },
    "simon-ruggier": {
      "name": "Simon Ruggier"
    },
    "sophie-van-der-kindere": {
      "name": "Sophie Van Der Kindere"
    },
    "stefanos-thampis": {
      "name": "Stefanos Thampis"
    },
    "stephen-weinberg": {
      "name": "Stephen Weinberg"
    },
    "sukhman-virk": {
      "name": "Sukhman Virk"
    },
    "sumaira-nazir": {
      "name": "Sumaira Nazir"
    },
    "sumer": {
      "name": "Sümer Cip"
    },
    "syed-raza": {
      "name": "Syed Raza"
    },
    "tamara-bacchia": {
      "name": "Tamara Bacchia"
    },
    "tara-arnold": {
      "name": "Tara Arnold"
    },
    "theosakamg": {
      "name": "Mickael Gaillard",
      "github": "theosakamg"
    },
    "thomasdiluccio": {
      "name": "Thomas di Luccio"
    },
    "tim-anderson": {
      "name": "Tim Anderson"
    },
    "tom-helmer-hansen": {
      "name": "Tom Helmer Hansen"
    },
    "tylermills": {
      "name": "Tyler Mills"
    },
    "upsun": {
      "name": "Upsun"
    },
    "veronika-tolkachova": {
      "name": "Veronika Tolkachova",
      "linkedin": "https://www.linkedin.com/in/veronika-tolkachova-169167a2"
    },
    "vince-parker": {
      "name": "Vince Parker"
    },
    "vinnie-russo": {
      "name": "Vincenzo Russo"
    },
    "vrobert78": {
      "name": "Vincent Robert",
      "github": "vrobert78",
      "linkedin": "https://www.linkedin.com/in/vincent-robert-498a883"
    },
    "yuriy-babenko": {
      "name": "Yuriy Babenko"
    },
    "yuriy-gerasimov": {
      "name": "Yuriy Gerasimov"
    }
  };
  return <div className="post-meta">
      {(authors.length > 0 || formattedDate) && <div className="post-meta-info">
          {authors.length > 0 && <div className="post-meta-authors">
              {authors.map(slug => {
    const {name, url, avatarUrl} = resolveAuthor(slug);
    const inner = <>
                    {avatarUrl && <img src={avatarUrl} alt={name} className="post-meta-avatar" />}
                    <span className="post-meta-author-name">{name}</span>
                  </>;
    return url ? <a key={slug} href={url} target="_blank" rel="noopener noreferrer" className="post-meta-author">
                    {inner}
                  </a> : <span key={slug} className="post-meta-author">{inner}</span>;
  })}
            </div>}
          {authors.length > 0 && formattedDate && <span className="post-meta-separator" aria-hidden="true">·</span>}
          {formattedDate && <span className="post-meta-date">{formattedDate}</span>}
        </div>}
    </div>;
};

<PostMeta data={{ author: ["crell"], date: "2020-10-14", image: "/images/posts/unknown/php-80-feature-focus-quality-of-life-improvements/php-80-feature-focus-quality-of-life-improvements.webp" }} />

<Tip>
  This post was originally published on the Platform.sh blog and reflects information from the time of publication.
</Tip>

<em>Our [last article](https://upsun.com/blog/php-8-0-type-improvements/) looked at a new way to make null values nicer to work with. PHP 8.0 has a lot of such features that make everyday coding more fun, but there are too many of them to give them all their own articles. So today we'll cover a grab bag of changes and improvements that aren't web-shattering but should make working with PHP overall more pleasant.</em>

## New string functions

PHP has a huge array of string functions (sorry, too easy), from the very useful to the rather obscure. PHP 8.0 brings three new functions to the table, all of which replace common past idioms.

`str_starts_with()` and `str_ends_with()` came as a matched set and do exactly what they say on the tin. Both take a string to check and a partial string to test and return a boolean if the first string begins with (or ends with) the second.

```php theme={null}
<?php
$needle = 'hello';
if (str_starts_with('hello world', $needle)) {
    print "This must be your first program.";
}
```

Both functions do an exact match, in ASCII, and have no case-insensitive option as that was deemed to complicate it too much. If you need a case-insensitive check, run `strtolower()` on both strings first. These functions come to us courtesy of an [RFC](https://wiki.php.net/rfc/add_str_starts_with_and_ends_with_functions) by Will Hudgins.

In a similar vein, `str_contains()` is also fairly self-explanatory. It checks if the first string contains the second string anywhere within it at all.

```php theme={null}
<?php
$needle = 'on';
if (str_contains('peace on earth', $needle)) {
    print "Yes, let's do that.";
}
```

As with the earlier functions, `str_contains()` is a case-sensitive ASCII comparison. The [RFC](https://wiki.php.net/rfc/str_contains) for this addition comes from Philipp Tanlak.

All three functions were possible to mimic in user-space via clever uses of `strpos()`, where "clever" is a polite way of saying "error-prone." One of the most common newbie PHP developer mistakes (made by many very experienced developers) was to do a `str_contains()` type check like so:

```php theme={null}
<?php

$its_in_there = strpos($haystack, $needle) == false;
```

However, due to PHP's type juggling, that basic-equality (`==`) check is insufficient, because a position of 0 (meaning the `$needle` is the first part of `$haystack`) would be `==` false. The check needs to be identity (`===`), which is easy to forget. It's also a perfect example of why "return a value or false on error" is a terrible pattern that [should never be used, ever](https://upsun.com/blog/php-8-0-type-improvements/).

Fortunately, with PHP 8.0 all of that cleverness is no longer needed, as the trio of new functions type-safely return the boolean value you wanted in the first place.

## `throw` expressions

PHP supports exceptions via the `throw` keyword. For various internal implementation reasons, `throw` was implemented as a *statement*, meaning it didn't return a value and just "was a thing."

In PHP 8, `throw` was converted to an *expression*, meaning it technically can return a value. That's not all that useful (`throw` breaks the current line of execution, by design), but it means `throw` can now be used in expression places in code.

The best example of where that's useful are ternaries, null coalesce, and other shortened expression symbols. Consider:

```php theme={null}
<?php

// $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException();

// $value is truthy.
$value = $falsableValue ?: throw new InvalidArgumentException();
```

In PHP 7, those are syntax errors. In PHP 8.0, they do what you'd expect: assign the value if possible or throw if the condition fails.

An even more fun combination is mixing it with the new [match() expression](https://upsun.com/blog/php-8-0-type-improvements/).

```php theme={null}
<?php

$value = match($size) {
  0 => throw new ThatsNotASize(),
  1 => throw new ThatsTooSmall(),
  3, 4 => 'small',
  5, 6 => 'medium',
  default => 'large',
};
```

`throw` expressions [come to us](https://wiki.php.net/rfc/throw_expression) courtesy of Ilija Tovilo.

## Non-capturing `catch`

Speaking of exceptions, there are cases where you want to catch an exception, but what you'll do in response doesn't depend on the exception data itself. You can now catch an exception without assigning it to a variable, like so:

```php theme={null}
<?php

try {
    changeImportantData();
} catch (PermissionException) {
    echo "You don't have permission to do this";
}
```

That `catch` will trigger for `PermissionException` exceptions, but the exception itself won't be assigned to a value.

Max Semenik was responsible for the contents of [this RFC](https://wiki.php.net/rfc/non-capturing_catches).

## Trailing commas in function definitions and closures

A long time ago on a mailing list far far away (that is, back in 2017), there was a proposal to allow a [trailing comma](https://wiki.php.net/rfc/list-syntax-trailing-commas) in all places where PHP supported a list of values. That, unfortunately, mostly failed, but subsequent RFCs to add comma support to each list construct independently have passed. Go figure. ¯\\\_(ツ)\_/¯

The latest additions for PHP 8 are in function definitions and closures. For function definitions, it's now possible to put a comma after the last parameter definition, which is mainly useful when many parameters are split across several lines.

```php theme={null}
<?php
Class Address
{
    public function __construct(
        string $street,
        string $number,
        string $city,
        string $state,
        string $zip,     // This is new.
    ) {
    // ...
    }
}
```

Trailing commas in function calls were already added in PHP 7.3.

[This update](https://wiki.php.net/rfc/trailing_comma_in_parameter_list) is thanks to the ever-popular Nikita Popov.

Trailing commas are now also allowed in `use` clauses in closures, for the same reason:

```php theme={null}
<?php
$a ='A';
$b = 'B';
$c = 'C';

$dynamic_function = function(
  $first,
  $second,
  $third,       // This is new, as above.
) use (
  $a,
  $b,
  $c,            // This is also now new.
);
```

This change comes [courtesy](https://wiki.php.net/rfc/trailing_comma_in_closure_use_list) of Tyson Andre.

## Token objects

Finally, we have a change that is a tab obscure but still very important for people doing code analysis or code generation.

PHP's [`token_get_all()`](https://www.php.net/manual/en/function.token-get-all.php) function lets PHP parse PHP code files and get back an array of arrays, representing the stream of tokens in that file. However, a giant array of arrays is not always the most useful way to represent, well, anything. For one, PHP arrays are [very memory hungry](https://peakd.com/php/@crell/php-use-associative-arrays-basically-never). For another, the nested arrays are positional, and the meaning of each position varies with the type of token. That architecture pattern is known in academic literature as "fugly."

PHP 8.0 introduces a new class, `PhpToken`, with a corresponding `getAll()` static method. `PhpToken::getAll()` is passed a string of PHP code and returns an array of `PhpToken` objects. Because it's using objects, this method is faster, uses half as much memory, and is easier to use than `token_get_all()`. (Tastes great *and* is less filling!)

`PhpToken` also includes a few utility methods to get information about the token, such as `getTokenName()` or `isIgnorable()`. ([The RFC has more details](https://wiki.php.net/rfc/token_as_object).) There's even a `__toString()` implementation that renders the object back to its source code string form. What's more, it's also possible to extend the class and add your own custom methods.

```php theme={null}
<?php

$code = '...'; // Some PHP source code.

class LowerCaseToken extends PhpToken implements Stringable {
    public function getLowerText() {
        return strtolower($this->text);
    }

    public function __toString(): string {
        return $this->getLowerText();
    }
}

$tokens = LowerCaseToken::getAll($code);

print implode($tokens);
```

In this (extremely contrived) example, `LowerCaseToken::getAll($code);` returns an array of `LowerCaseToken` objects. `implode()` then converts each to a string and concatenates them, with no extra separator. "Converts to a string" means that `__toString()` is called, which returns a lower-case version of the string representation of the token. The net result is to take a piece of code and force it to all lowercase without otherwise modifying the code or its structure.

This is admittedly not the most practical example, but one could imagine reading in a file of PHP source code, modifying the array in some structured way, and then trivially serializing it back out in modified form, while still respecting the structure and alignment of the text. Alternatively, one could add additional methods to help with advanced parsing for code formatting, static analysis, or other meta-coding tasks.

It should be no surprise that [this RFC](https://wiki.php.net/rfc/token_as_object) comes to us courtesy of Nikita Popov. Again.

<em>Tune in next week for another grab-bag of smaller changes that make PHP more predictable and logical but might impact your code.</em>

<em>You can [try out pre-release copies of PHP 8.0](/posts/unknown/pre-release-php-8-0-images-now-available) today on Platform.sh, with just a one-line change. Give it a whirl, and let us know what your favorite features are.</em>
