` only when nothing more meaningful fits.
::
## Sectioning Elements
As defined in the HTML 5 specification, Sectioning content defines the scope of headings and footers. The following tags are used to define sections within your document.
- article
- main
- aside
- nav
- section
- header
- footer
Each element has a different meaning though as to what kind of data is expected.
### Article Element
The article element, you can think of as a self-contained element. Meaning that if you were to remove the element with its containing content, that the rest of the document would still work just fine. Examples of this could be a blog post, a product on a category page, or really any other stand alone item of content.
You can also nest article elements within other article elements. When nesting article elements, the inner article elements would be considered elements related to the parent article element. For instance, comments on a blog post.
### Main Element
When you have a known area of content that is considered the primary content of the page, you should mark it up with the main element instead of article. You can still use article as well, but it would be considered redundant.
### Aside Element
The aside element should be used to surround a section of the document that contains information that is related to the sibling content, but not directly about it. For instance, you may see sidebars as a common use case where the side bar contains related links regarding the primary information on the page.
Another common use case for the aside tag would be with advertisements. For instance in the case that you have some ad space on your page that is related to the information you have posted, but you want it to be kept separate from the main content.
### Nav Element
The nav link should be used to surround a section of a page that contains links to other pages or areas of the same page. For instance, your top navigation for your website.
One thing to note though is that the nav element should not go around all groupings of links on your pages. Instead you should only use the nav tag to surround major navigational blocks. Also, let's assume you have your categories within your primary navigation of the website. If you also have those same categories in your footer, then there is no need to wrap them in the nav element.
### Section Element
The section element is one that is commonly confused with article. The section element should only be used to represent a generic section of a page. Think of it this way, if there is content on your page that should be syndicated then use article, if not you can use section. For instance, you may want to split up your homepage into sections, since each section of a homepage is typically generic.
### Header Element
The header element is used for grouping the following information:
1. Branding Information
2. Heading Information
3. Navigational Aids
Not only can this tag be used for the header of your website, but it is also used to mark the introductory areas of each of your sections.
For instance, let's assume we have a category page with products on it. On our category page we have our category name, category short description, and maybe some related category links. You could essentially group this section with our header, as it's the start of our category page.
Header tags can be used within sectioning elements or sectioning root elements.
### Footer Element
You can have multiple footer elements, as you can with header elements. The footer element represents the footer for its nearest ancestor. It normally contains information like:
- Author Information
- Copyright Information
- Related Links
## Sectioning Roots
Sectioning roots are used to group sets of information that are not needed within the document outlines of its ancestors. In fact, any markup placed within a sectioning root element is completely ignored by its parent document structure.
Below are the tags considered as sectioning root elements.
- blockquote
- body
- fieldset
- figure
- td
## Testing Your Structure
A nice browser tool that you can use in either Firefox or Chrome is *HeadingsMap*. This extension allows you to see your current document outline in both the latest HTML 5 overview, as well as the previous HTML 4 outline structures.
### Features Include
- Shows structure of the current document's headers.
- Displays the outline of each of the headers.
- Alerts you if there is a section without a header.
# Support for keys in list(), or its new shorthand syntax [] in PHP
Now as of PHP 7.1, you can define the keys of your array that will be parsed when destructuring your arrays. Prior to PHP 7.1, you could only use arrays with numeric indexes. Now with this new addition, our lives just got easier.
Let's go over a few examples below on how we can use this new feature.
**Example**
Let's start by setting up our array.
```php
1,
'age' => 29,
'name' => 'Frank',
],
[
'id' => 2,
'age' => 28,
'name' => 'Sarah',
],
[
'id' => 3,
'age' => 27,
'name' => 'Michael',
],
];
```
Now that we have our array setup, we'll want to start by destructuring the first person in the array.
```php
$id, 'name' => $name, 'age' => $age) = $person;
echo 'ID: ' . $id . '
';
echo 'Name: ' . $name . '
';
echo 'Age: ' . $age . '
';
```
You'll notice that I was able to specify the keys above, and also that they do not need to be in the same order as the original array. Since our keys match up with the keys in our array, our application is now smart enough to handle that.
Let's rewrite the above to use the shorthand syntax now.
```php
$id, 'name' => $name, 'age' => $age] = $person;
echo 'ID: ' . $id . '
';
echo 'Name: ' . $name . '
';
echo 'Age: ' . $age . '
';
```
::note{label="From 2026"}
Keyed destructuring has been baseline since PHP 7.1, and the `[]` shorthand is the form you will see most often today. You can also nest it, for example `['name' => $name, 'address' => ['city' => $city]] = $person;`, and use it directly in a `foreach` to unpack each row.
::
# Symmetric Array Destructuring in PHP
As of PHP 7.1, you can now use the shorthand array syntax to destructure your arrays for assignment. Previously you would have had to use a function like list, but now you can use the simple new array shorthand syntax.
::note{label="From 2026"}
This was new in PHP 7.1 back in 2016. The shorthand `[...]` destructuring syntax is now standard and works everywhere on modern PHP (8.4 at the time of writing).
::
Let's look at some examples.
**Basic Example**
```php
';
}
```
# The grill-me Skill for Claude Code
You have probably hit this one. You type a paragraph describing some feature, Claude reads it, and half a minute later it is off building. The thing it builds answers the prompt you wrote, not the thing you actually had in mind. The requirements were fuzzy in your head, so they came out fuzzy on the keyboard, and the model just filled the gaps with reasonable-looking guesses.
`grill-me` turns that around. Rather than you briefing Claude so it can start coding, Claude interviews you first. It pulls your plan apart one question at a time until there is nothing left that is vague enough to build wrong. I went in expecting a gimmick and came out using it most weeks.
## What grill-me actually is
It is a Claude Code *skill*, which is just a small `SKILL.md` file that Claude finds and loads by itself when the moment fits. Matt Pocock wrote it (you may know him from Total TypeScript), and it lives in his public [`mattpocock/skills`](https://github.com/mattpocock/skills){rel=""nofollow""} repo, which is basically a dump of the skills from his own `.claude` directory.
The skill is community-made, not something Anthropic ships, and it took off. The repo collected tens of thousands of stars after he posted about it, and `grill-me` was the one everyone fixated on. Partly that is because it names a problem people run into constantly, and partly because there is almost nothing to it.
## The whole skill is three sentences
That last point is worth dwelling on. No scripts, no scaffolding, no tooling. The entire skill is a description and three sentences of instruction:
```markdown
---
name: grill-me
description: Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me".
---
Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.
Ask the questions one at a time.
If a question can be answered by exploring the codebase, explore the codebase instead.
```
That is the whole file, sitting at `skills/productivity/grill-me/SKILL.md`. All the work is being done by the prompt. The line that carries the weight is "walk down each branch of the design tree." It tells Claude to treat your feature as a tree of decisions, where every choice opens up a few more, instead of one block of text to skim and act on. The other two sentences just keep things sane: ask one question at a time, and go read the code rather than asking when the repo already knows the answer.
## What it feels like to use
You kick it off by saying "grill me" (or running `/grill-me`) next to whatever you want torn apart. Then the roles swap. Claude starts asking, and it does not run out of questions at the convenient point.
A few details make it work:
- **One question at a time.** You are not handed twenty bullet points to clear in one go. Each question gets its own turn, so you have to actually think about it rather than skim past.
- **It brings its own answer.** Every question comes with the option Claude would pick and why. Reacting to a concrete proposal is a lot easier than producing one from nothing, which is what keeps the session moving.
- **It reads the code first.** When the repo can settle a question, Claude goes and looks instead of making you recite something that is already written down.
- **It keeps going.** It works down the branches until you genuinely agree on the plan, not until the conversation feels long enough.
The annoying questions are the useful ones. "What happens when this fails halfway through?" is exactly the kind of thing you were quietly planning to deal with later. Answering it before any code exists is far cheaper than running into it three files deep.
## Installing it
The fastest way is Pocock's installer, which grabs the skills from the repo and lets you choose what to add:
```bash
npx skills@latest add mattpocock/skills
```
If you would rather skip the installer, remember a skill is just a file in a folder. Save the `SKILL.md` above to `~/.claude/skills/grill-me/SKILL.md` and Claude Code will find it on its own, then call it with `/grill-me`. Every skill works the same way, a `SKILL.md` sitting in its own folder under `~/.claude/skills/`. If you want to see how skills and plugins hang together, I wrote up [how to build a Claude Code plugin](https://franktheprogrammer.com/articles/how-to-build-a-claude-code-plugin) separately.
## Beyond coding, and grill-with-docs
None of this is really about code. Any time your thinking is messier than you want to admit, whether it is a product call, a piece of writing, or an architecture you cannot quite commit to, being walked down the decision tree drags the fuzzy bits into the light. Pocock has said he uses it well outside of programming, which makes sense once you notice it is a thinking tool that happens to be wearing a coding hat.
For codebases there is a follow-up called `grill-with-docs`. It grills you the same way, but as decisions firm up it writes them down: a `CONTEXT.md` glossary of your project's terms, plus Architecture Decision Records dropped in as you go. So the grilling stops being a single conversation and starts to pile up into shared context you keep between sessions. There are community forks too (`Jekudy/grillme-skill` is one), which tends to happen when an idea lands.
## Worth it?
For anything bigger than a one-liner, yes. The cheapest tokens you spend are the ones on planning, and the most expensive bug is the one you confidently build from a vague brief. `grill-me` is a structured way to pay the small cost first. If that is your kind of thing, [getting more out of Claude Code](https://franktheprogrammer.com/articles/getting-more-out-of-claude-code) goes deeper on prompting and token economy for the same basic reason: sort the plan out before you spend the budget building it.
# Troubleshooting Xcode 15 Build Issues in Flutter Projects
Updating to Xcode 15 brought a few hurdles in my Flutter project, especially since I was using an older version of CocoaPods, below version 1.13. Here's a breakdown of how I navigated through these issues and got my project back on track.
## Step 1: Update Podfile (Flutter / Xcode 15)
Update your Podfile with the following snippet:
```ruby
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
xcconfig_path = config.base_configuration_reference.real_path
xcconfig = File.read(xcconfig_path)
xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR")
File.open(xcconfig_path, "w") { |file| file << xcconfig_mod }
end
end
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
end
end
end
```
## Step 2: Update Build Phases
1. Open Xcode
2. Click "Runner" in the project navigator
3. Navigate to Targets *(Runner in the sidebar)*
4. Click on Build Phases *(found in the top bar)*
5. Move "Embed App Extension" above the Run Script step
## Step 3: Run Flutter Clean
Once the above changes are done, clean your project with the following command:
```bash
flutter clean
```
You should now be able to successfully build and run your project.
::note{label="From 2026"}
These workarounds are specific to the Xcode 15 / older-CocoaPods era of 2023. Newer Flutter and Xcode toolchains have eased most of this, so on a current setup you likely won't need the `DT_TOOLCHAIN_DIR` Podfile patch.
::
# Type Hinting Callable Functions in PHP
As of PHP 5.4, you can type hint your method arguments with the callable keyword allowing you to enforce the type of data that is passed via your arguments.
Let's look at an example where we create a Sort class, with a custom method that takes a callable function so that we can customize how our data is sorted.
**Example**
```php
$number2;
});
```
That's all there is to it. Now we can pass any function as a second argument to our method allowing us to pass any custom sorting algorithm to our class.
# Type Hinting with Nullable Types in PHP
As of PHP 7.1, you can now set your type declarations as nullable by simply prefixing them with a question mark ?. In doing so a null value can be passed in as a parameter or returned as a value for your methods.
Let's look at an example where we setup a simple product class that can pass a name via the constructor. We will type hint the variable to force the use of a string, but we'll also set it as a nullable type this way a null value can be passed.
```php
name = $name;
}
public function name() : ?string
{
return $this->name;
}
}
```
Now let's look at how we would use the above to pass a nullable value to the constructor.
```php
name());
```
The above code would output a response of *NULL*.
# Type Hinting With The Iterable pseudo-type In PHP
As of PHP 7.1, you can now type hint your method/function arguments with the keyword iterable for handling arrays or even objects that implement the Traversable interface.
Let's create an example where we can convert any string to a slug. We'll have 2 methods so that we can pass either a string or iterable data.
We'll start by setting up our SlugGenerator class.
```php
titles, $title);
}
public function getIterator() : Iterator
{
return new ArrayIterator($this->titles);
}
}
$titles = new Titles;
$titles->add('Test Title 1');
$titles->add('Test Title 2');
$titles->add('Test Title 3');
$slugs = SlugGenerator::byArray($titles); // Outputs array of slugs
```
You'll see that we're able to use the same SlugGenerator class without having to modify our original class. Awesome, right?
# Using Browser Sync with Gulp for Live Reloading
Browser Sync is a nice tool to use while developing. It allows your browser to reload live, when changes are made to your files. For instance, assuming we're watching our CSS file for changes we can have the browser auto refresh/sync when it sees those changes made.
In this article, I'll show you the basics of how to get started with Browser Sync. Feel free to read up on the documentation for a more in depth look at all of the configuration options available.
[Browser Sync Documentation](https://www.browsersync.io/docs){rel=""nofollow""}
::note{label="From 2026"}
Modern dev servers like [Vite](https://vite.dev/){rel=""nofollow""} ship with built-in live reload and hot module replacement, so a separate BrowserSync plus Gulp setup is rarely needed today.
::
## Installation Steps
First thing we'll need to do is setup our project. For this project we'll be using gulp along with browser-sync for our packages.
Let's start by installing gulp to our project.
```bash
yarn add gulp
```
Next, we'll want to install the browser-sync plugin.
```bash
yarn add browser-sync
```
## Configuration
Now that we have our 2 plugins installed, let's go ahead and configure our gulpfile.js file.
We'll first want to include the 2 packages that we recently installed.
```javascript
var gulp = require('gulp');
var browserSync = require('browser-sync').create();
```
Next, let's setup a task called watch, which will look for changes in our specified CSS file.
```javascript
gulp.task('watch', function() {
browserSync.init({
proxy: "bootstrap.dev"
});
gulp.watch("css/*.css").on('change', browserSync.reload);
});
```
You'll note above that I have setup a local domain for testing called bootstrap.dev. The proxy basically lets browser-sync know that it should load the website from that given domain.
From the example above, you'll see that we need 2 parts for this to work.
1. We need to initiate browser sync.
2. After we initiate it, we'll then want to set our watcher on our CSS file, and let it know when something changes to reload the browser.
You can monitor changes on any files you'd like, for instance HTML or even JavaScript.
## Testing
Once we're ready to test, we can simply run the command gulp watch to see our changes take effect. We can now edit our CSS file and see the changes happen live within our browser.
When you run gulp watch, you'll notice that our browser automatically opens up with the website connected to our browser sync instance.

There are many more configuration options available to use, and I urge you to go through the documentation so that you can see fully how Browser Sync can improve your development process.
# Using CSS Transitions
CSS transitions are the standard way to apply transitions to your elements, and have been for years, replacing the old approach of using JavaScript. In this article, I'll go through each of the transition properties available, and provide examples of how to use them.
Below is a list of browser prefixes that were once used to enable backward compatibility with older browsers for each of the properties we will be discussing.
**Browser prefixes:**
- -webkit- , provides backward support for webkit browsers such as Chrome.
- -moz- , provides backward support for Firefox.
- -ms- , provides backward support for I.E.
- -o- , provides backward support for Opera.
::note{label="From 2026"}
You can skip almost all of these today. Every current browser supports `transition` and the properties below without a prefix, so the bare version is all you need. The `-moz-`, `-ms-`, and `-o-` prefixes are obsolete, and even `-webkit-` is rarely necessary for transitions. If you ever do need to support ancient browsers, let a build tool like Autoprefixer add the prefixes for you instead of writing them by hand. I've kept the prefixed example below for reference.
::
There are a handful of properties that we will be using for this article. One is considered the short hand version, where you can combine the values of each property into one line.
**The available transition properties are:**
- transition (short hand)
- transition-duration
- transition-property
- transition-delay
- transition-timing-function
We'll discuss the shorthand version at the end, that way I can show you how each individual property works and how we can bring them together later.
## Transition Duration
The transition-duration property is super important, for transitions to work in general. Without setting the duration, you'd never see any of the transitions since the default value is 0. This property specifies how many seconds (s) or milliseconds (ms) a transition effect should take to complete.
## Transition Property
The transition-property property allows you to specify the name of a CSS property that the transition should be applied to. For instance, if you wanted to apply a transition to the width property, then you'd simply use width as your value for this property.
You can also apply transitions to multiple properties by using a comma to separate them. Below is a list of possible properties you may use.
- none: This disables the transition effect for your element.
- all : This is the default value, and lets our document know to look at all properties for transition changes.
- property: You can specify the properties you want to apply transitions to by separating them by comma.
- initial : This one will set the property to its default value.
- inherit : Use this one, to inherit the transition property from its parent element.
## Example Usage for Transition Property and Transition Duration
Let's assume we have a box, that we'd like to change the width and height of using a transition on hover.
**Let's look at our HTML first.**
```html
```
Next we'll look at setting up our CSS without the backward browser support.
```css
.box {
width: 150px;
height: 150px;
background-color: blue;
transition-property: width, height;
transition-duration: 2s;
}
.box:hover {
width: 450px;
height: 250px;
}
```
And that's all you need. When you open the browser now and hover over the box you've created, you'll see that it now animates to the new width and height on hover. How cool is that?
Let's look at the CSS with the backward browser support now for your reference:
```css
.box {
width: 150px;
height: 150px;
background-color: blue;
-webkit-transition-property: width, height;
-moz-transition-property: width, height;
-ms-transition-property: width, height;
-o-transition-property: width, height;
transition-property: width, height;
-webkit-transition-duration: 2s;
-moz-transition-duration: 2s;
-ms-transition-duration: 2s;
-o-transition-duration: 2s;
transition-duration: 2s;
}
.box:hover {
width: 450px;
height: 250px;
}
```
For the next examples, I will not be including the backward browser support versions in order to keep the code examples small. Keep in mind though, that you'll want to use the prefixed versions in your actual code base for usability and consistency across various browser versions.
## Transition Delay
Let's assume now that you do not want the animation to start right away. Instead you want to add a delay of 500 milliseconds. Luckily this is easy to accomplish with the transition-delay property.
Let's have a look by continuing on our previous example with the box styles.
```css
.box {
width: 150px;
height: 150px;
background-color: blue;
transition-property: width, height;
transition-duration: 2s;
transition-delay: 500ms;
}
.box:hover {
width: 450px;
height: 250px;
}
```
Now when you refresh your browser and hover over your box, you'll see that there is a slight delay before the animation starts. AMAZING!
## Transition Timing Function
Now the transition-timing-function property allows you to specify the speed at which the transition changes over the duration of time. This is great to make the transitions seem a bit more real.
Here's the supported list of values for this property below.
- ease: This is the default behavior. The animation will start slower, then speed up, and end slowly.
- linear: The animation will maintain the same speed from start to end.
- ease-in: The animation will start slow and then speed up.
- ease-out: The animation will start fast and then slow down.
- ease-in-out: The animation will start slow and end slow, but speed up in between.
- step-start: Alias to steps start value.
- step-end: Alias to steps end value.
- steps(int,start|end): Steps defines the number of steps it will take for a transition to complete. The first parameter is where you define the number of steps, and the second parameter defines when the values of a property should change. Either at the start of the transition step, or at the end, after the step completes.
- cubic-bezier(n,n,n,n): This one gives you even greater control of the transition. Each n value should be numeric between and including 0 to 1.
- initial: This will set the property to its default value.
- inherit: This will inherit the value of its parent element.
Let's look at some examples.
First we'll have our box resize using the linear value. In doing so you'll notice that the speed of the transition is consistent throughout the entire duration of the transition.
```css
.box {
width: 150px;
height: 150px;
background-color: blue;
transition-property: width, height;
transition-duration: 2s;
transition-delay: 500ms;
transition-timing-function: linear;
}
.box:hover {
width: 450px;
height: 250px;
}
```
Let's look at an example using steps now. Steps allows us to define how many steps it will take until finishing the transition.
```css
.box {
width: 150px;
height: 150px;
background-color: blue;
transition-property: width, height;
transition-duration: 2s;
transition-delay: 500ms;
transition-timing-function: steps(6, end);
}
.box:hover {
width: 450px;
height: 250px;
}
```
Here we're using steps(6, end), which lets our transition know that it should complete in exactly 6 steps. We passed end as our second parameter, letting our transition know that each step should change the values at the end of the interval instead of the start.
## Transition: The Shorthand Version
Now that we've covered each of the different property types available for transition, Let's look at how we can shorten our code using the short-hand version.
Here's a break down of the transition property and in what order each property value should go.
- transition: property duration timing-function delay;
We'll take our same styles from the box example with the linear transition and convert it to the short hand version.
```css
.box {
width: 150px;
height: 150px;
background-color: blue;
transition: width 2s linear 500ms,
height 2s linear 500ms;
}
.box:hover {
width: 450px;
height: 250px;
}
```
You'll see above that we were able to separate each transition properties by a comma in order to affect width and height. The code is now much shorter.
# Using Functions and Mixins with Stylus
::note{label="Stylus series"}
Part of my [Learning Stylus](https://franktheprogrammer.com/articles/learning-stylus-a-css-pre-processor) series. Stylus still works in 2026, but its ecosystem has largely moved on to Sass, PostCSS, and native CSS; the series intro has the full context.
::
Stylus allows you to create functions and mixins of reusable code for your stylesheets. You can also handle mathematical operations, unary operations, and more allowing you complete control over your stylesheets with ease.
Mixins are synonymous with functions. What determines when you call something a mixin or function is based on its usage. Mixins by definition are invoked as a statement, where functions are part of an expression.
## Constructing A Function / Mixin
Let's assume you're wanting to create a function to handle rounding corners of an element. We can simply name it border-radius, to keep it in line with the actual syntax. Only now when we'd call border-radius within our code it would generate the code with support for various browsers.
Let's create a box as our example with rounded corners.
**Stylus Code**
```stylus
border-radius()
-webkit-border-radius: arguments
-moz-border-radius: arguments
border-radius: arguments
.box
width: 100px
height: 100px
background-color: blue
border-radius(10px)
```
**Stylus Output**
```css
.box {
width: 100px;
height: 100px;
background-color: #00f;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
}
```
Notice how the output now contains the 3 lines required to generate rounded corners for multiple browser support. Another thing to note is that we were able to pass the value of 10px to each line by calling the arguments keyword. This will allow us to reuse the function throughout our entire code base.
We can even take this a step further, and make the function a little more transparent by losing the parenthesis altogether. This is the beauty of using mixins, as it allows you to transparently add support for various browsers while keeping the syntax the same.
**Stylus Code**
```stylus
border-radius()
-webkit-border-radius: arguments
-moz-border-radius: arguments
border-radius: arguments
.box {
width: 100px
height: 100px
background-color: blue
border-radius: 10px
}
```
**Stylus Output**
```css
.box {
width: 100px;
height: 100px;
background-color: #00f;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
}
```
Notice we still get the same result, and it's a little more natural writing it this way.
Let's look at another example of creating a mixin where we alternate the background colors of our rows of a table.
**Stylus Code**
```stylus
stripeRows(even = #eeeeee, odd = #dddddd)
tr
background-color: odd
&:nth-child(even)
background-color: even
table
stripeRows()
```
**Stylus Output**
```css
table tr {
background-color: #ddd;
}
table tr:nth-child(even) {
background-color: #eee;
}
```
## Argument Defaults
You can also set default values for your arguments if one is not provided. Let's look at the border-radius example, and add a sensible default if a value is not provided.
**Stylus Code**
```stylus
border-radius(radius = 10px)
-webkit-border-radius: radius
-moz-border-radius: radius
border-radius: radius
.box
width: 100px
height: 100px
background-color: blue
border-radius()
```
Notice that we set the default value to 10px, so if no value is set the border radius would be set to 10px by default.
## Named Parameters
You can also pass named parameters to your function as well. The benefit to this approach is if you have more than one parameter to your function you're not required to remember the order of how the arguments should be passed.
**Stylus Code**
```stylus
.box
width: 100px
height: 100px
background-color: blue
border-radius(radius: 10px)
```
## Multiple Return Values
Interestingly enough, a function can also return multiple values as well. Let's look at an example where we create a perfect square box.
**Stylus Code**
```stylus
squareBox(x, color = blue)
dimensions = squareCoordinates(x)
box(dimensions[0], dimensions[1], color)
squareCoordinates(x)
return x x
box(x, y, color = blue)
width: x
height: y
background-color: color
.box
squareBox(150px)
```
**Stylus Output**
```css
.box {
width: 150px;
height: 150px;
background-color: #00f;
}
```
You'll notice that now we only have to call the function squareBox() and pass the size, and the rest of the box code will be generated for us.
## Conditionals
Like other programming languages, we can also take advantage of conditionals. This can be useful to us in cases where we'd need some set of styles applied in certain cases and not others.
Let's look at an example where if someone creates a box which is not a perfect square we add some extra margin to the needed side to make it square.
**Stylus Code**
```stylus
box(x, y, color = blue)
width: x
height: y
background-color: blue
if(x > y)
margin: ((@width - y) / 2) 0
else
margin: 0 ((@height - x) / 2)
.box
box(50px, 100px)
float: left
```
**Stylus Output**
```css
.box {
width: 50px;
height: 100px;
background-color: #00f;
margin: 0 25px;
float: left;
}
```
## Anonymous Functions
You can also create anonymous functions using the @(){} syntax. The [Stylus documentation on functions](https://stylus-lang.com/docs/functions.html){rel=""nofollow""} has a good example built around a sort function.
## Arguments
The arguments keyword is available within each function block. It will contain the list of all arguments passed to the function.
In one of our previous examples of the border-radius, you found that we were using the keyword arguments instead of a variable name. This allowed us to pass any parameters/arguments we need to our function without manually specifying each allowed argument .
**Stylus Code**
```stylus
border-radius()
-webkit-border-radius: arguments
-moz-border-radius: arguments
border-radius: arguments
.box {
width: 100px
height: 100px
border-radius(10px)
}
```
**Stylus Output**
```css
.box {
width: 100px;
height: 100px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
}
```
# Using Selectors in Stylus
::note{label="Stylus series"}
Part of my [Learning Stylus](https://franktheprogrammer.com/articles/learning-stylus-a-css-pre-processor) series. Stylus still works in 2026, but its ecosystem has largely moved on to Sass, PostCSS, and native CSS; the series intro has the full context.
::
Selectors are a way to pick the elements that you want styled. In Stylus, similar to CSS, you can apply a set of styles to any element by separating them by a comma delimited list. Stylus though, also allows you to select multiple elements by separating each on their own line.
Let's look at an example of each to see how this works.
**Stylus (comma delimited)**
```stylus
b, .strong
color: green
font-style: italic
```
**Stylus (newline delimited)**
```stylus
b
.strong
color: green
font-style: italic
font-weight: bold
```
**Compiled CSS (same output for both examples above)**
```css
b,
.strong {
color: #008000;
font-style: italic;
font-weight: bold;
}
```
## Parent Selector
You can also reference the parent elements for a particular style by using the & ampersand symbol. For instance, let's assume you want to add a hover for color changes to some elements.
If you need to use the ampersand symbol within a selector without it referencing the parent, then you can use the backslash character to escape it. &
**Stylus Code**
```stylus
textarea, input
color: lightgray
&:hover
color: gray
```
**Stylus Output**
```css
textarea,
input {
color: #d3d3d3;
}
textarea:hover,
input:hover {
color: #808080;
}
```
## Partial Reference
Partial selectors ^ [N] can be used anywhere within your selectors allowing you to reference a specific level of the parent where N is your numeric level starting at 0, which is the highest element within your chain. When rendered, they contain the entire chain of your selectors until the specified nested level.
If you use a positive number then the higher the number, the closer to your actual element in the chain you're styling you are.
If you use a negative number, it would be the complete reverse. Instead of starting from the first element in your chain, you would start from the current element and reversely traverse the chain.
They are especially useful when using them within your mixins when you are unaware of the nesting level your mixin may be called from.
Let's look at an example to see how this can be used in application.
We'll start by setting up our HTML
**HTML Code**
```html
```
Now, let's make it so that when you hover over any part of the title that the word "Here" changes from one color to another.
**Stylus Code**
```stylus
.page-header
color: darkgreen
&--highlight-word
color: lightgreen
^[0]:hover &
color: greenyellow
```
You'll see that we were able to select ".page-header" and state that on hover of that element apply the color change to the element `&--highlight-word`.
**Stylus Output**
```css
.page-header {
color: #006400;
}
.page-header--highlight-word {
color: #90ee90;
}
.page-header:hover .page-header--highlight-word {
color: #adff2f;
}
```
## Initial Reference
A shortcut really to ^ [0], the initial reference \~/ can only be used at the start of your selector. We can update our example code above to take advantage of this shortcut like so.
**Stylus Code**
```stylus
.page-header
color: darkgreen
&--highlight-word
color: lightgreen
~/:hover &
color: greenyellow
```
**Stylus Output**
```css
.page-header {
color: #006400;
}
.page-header--highlight-word {
color: #90ee90;
}
.page-header:hover .page-header--highlight-word {
color: #adff2f;
}
```
## Relative Reference
The relative reference ../ can also only be used at the start of your selectors. They can be used to go as far up the chain as you'd like.
**Stylus Code**
```stylus
.page-header
color: darkgreen
&--highlight-word
color: lightgreen
../:hover &
color: greenyellow
```
**Stylus Output**
```css
.page-header {
color: #006400;
}
.page-header--highlight-word {
color: #90ee90;
}
.page-header:hover .page-header--highlight-word {
color: #adff2f;
}
```
## Root Reference
The root selector /, is a reference to your document root. For instance, let's assume you had a set of styles that you also wanted to assign to a root level class.
In our example we'll create a class .hover-color, which does just that. On hover it changes the color of the text it is applied to. We'll just append to our existing code to show off how to do this.
**Stylus Code**
```stylus
.page-header
color: darkgreen
&--highlight-word
color: lightgreen
~/:hover &,
/.hover-color:hover
color: greenyellow
```
**Stylus Output**
```css
.page-header {
color: #006400;
}
.page-header--highlight-word {
color: #90ee90;
}
.page-header:hover .page-header--highlight-word,
.hover-color:hover {
color: #adff2f;
}
```
## selector() and selectors()
There are 2 functions available to you as well that allow you to get the currently rendered selectors from the current chain.
Here's an example using the selector() function.
**Stylus Code**
```stylus
.page-header
color: darkgreen
&--highlight-word
color: lightgreen
content: selector()
```
**Stylus Output**
```css
.page-header {
color: #006400;
}
.page-header--highlight-word {
color: #90ee90;
content: '.page-header--highlight-word';
}
```
You'll see that the content is now filled with the current rendered class name.
If we were to change the above from selector() to selectors(), you'd see that all of the classes from the chain would appear.
**Stylus Code**
```stylus
.page-header
color: darkgreen
&--highlight-word
color: lightgreen
content: selectors()
```
**Stylus Output**
```css
.page-header {
color: #006400;
}
.page-header--highlight-word {
color: #90ee90;
content: '.page-header', '&--highlight-word';
}
```
You can also use the selector() function to create your nested selector structure.
**Stylus Code**
```stylus
{ selector('.page-header') }
color: darkgreen
{ selector('.page-header', '&--highlight-word') }
color: lightgreen
{ selector('.page-header', 'span') }
font-weight: bold
```
**Stylus Output**
```css
.page-header {
color: #006400;
}
.page-header--highlight-word {
color: #90ee90;
}
.page-header span {
font-weight: bold;
}
```
# Void Return Types in PHP
As of PHP 7.1, we can now use void return types within our methods. This is useful for cases where you have methods that are just setting or processing data without the need of returning any values.
Functions or Methods declared with void as their return type would simply use an empty return statement or not use a return statement at all.
Let's look at an example below where we create a Product class capable of setting a product's name through the constructor or a setName method. The setName method would have a return type of void, since it's only setting a value and not set up to return anything.
```php
name = $name;
}
public function setName(string $name) : void
{
$this->name = $name;
}
public function name() : string
{
return $this->name;
}
}
```
You could also just set a blank return statement like so.
```php
public function setName(string $name) : void
{
$this->name = $name;
return;
}
```
# What's New in PHP 8.0
PHP 8.0 landed on 26 November 2020, and unlike the 7.x line it really earned the major version bump. It is the release that brought the JIT compiler, but the changes you actually feel day to day are the language features: less boilerplate, stricter types, and a handful of constructs that make everyday code read better. Let's walk through the ones I reach for most.
## Constructor property promotion
For years, declaring a value object meant writing every property three times: once as a field, once as a constructor parameter, once as an assignment. Promotion collapses all three into the parameter list.
```php
final class Money
{
public function __construct(
public readonly int $amountCents,
public readonly string $currency = 'USD',
) {}
}
$price = new Money(1999, 'EUR');
echo $price->amountCents; // 1999
```
The `public readonly` in front of each parameter declares the property, sets its visibility, and assigns it for you. A class that used to be fifteen lines is now five, and there is nowhere for a typo between the parameter and the field to hide.
## Named arguments
Named arguments let you pass values by parameter name instead of position. They pair beautifully with functions that have a long tail of optional settings, because you can skip straight to the one you care about.
```php
function createUser(
string $email,
bool $active = true,
bool $verified = false,
string $role = 'member',
): User {
// ...
}
$user = createUser('frank@fjp.io', role: 'admin', verified: true);
```
No more passing `true, false, true` and counting commas to remember which flag is which. The call site documents itself, and you only name the arguments you are changing from their defaults.
## The match expression
`match` is like `switch`, but it is an expression that returns a value, it compares with strict `===`, and it has no fall-through. That combination kills a whole category of bugs.
```php
$label = match ($response->status) {
200, 201, 204 => 'Success',
301, 302 => 'Redirect',
404 => 'Not Found',
500 => 'Server Error',
default => 'Unknown',
};
```
Each arm is a single expression, multiple values share an arm with a comma, and an unmatched value with no `default` throws instead of silently doing nothing. If you have used the [spaceship operator](https://franktheprogrammer.com/articles/php-spaceship-operator) or the [null coalescing operator](https://franktheprogrammer.com/articles/php-null-coalescing-operator), this is the same spirit: small syntax that removes ceremony.
## The nullsafe operator
Reaching through a chain of objects that might be null used to mean a ladder of `if` checks or nested ternaries. The nullsafe operator `?->` short-circuits the whole chain to `null` the moment any link is missing.
```php
$country = $user?->getAddress()?->country;
```
If `$user` is null, or `getAddress()` returns null, the expression stops and yields `null` rather than throwing on a method call against nothing. It reads top to bottom, exactly the way you would describe it out loud.
## Union types
PHP 8.0 made union types a native, runtime-checked part of declarations. You can now say a value is one of several types directly in the signature, rather than dropping to a docblock and hoping callers read it.
```php
function pad(int|string $value, int $width): string
{
return str_pad((string) $value, $width, '0', STR_PAD_LEFT);
}
pad(7, 4); // "0007"
pad('42', 4); // "0042"
```
This builds on the [scalar type hints](https://franktheprogrammer.com/articles/scalar-type-hints-php) and [return type declarations](https://franktheprogrammer.com/articles/return-type-declarations-in-php) PHP gained earlier. Where you used to write `?int` to mean "int or null" (see [nullable types](https://franktheprogrammer.com/articles/type-hinting-with-nullable-types-in-php)), you can now express far richer combinations, and the engine enforces them.
## String helpers that read like English
Checking what a string contains used to mean `strpos($haystack, $needle) !== false` and remembering that `0` is a valid position. PHP 8.0 finally shipped the three functions everyone had been hand-rolling.
```php
$file = 'invoice-2020.pdf';
str_contains($file, '2020'); // true
str_starts_with($file, 'inv'); // true
str_ends_with($file, '.pdf'); // true
```
They return a clean boolean, so no more `!== false` dance. As a bonus, `throw` became an expression in 8.0, so you can guard a value inline:
```php
$config = $options['dsn'] ?? throw new InvalidArgumentException('Missing dsn');
```
PHP 8.0 set the tone for the entire 8.x line: lean on the type system, write less glue, and let small language features carry the weight that boilerplate used to. Almost everything that follows in 8.1 through 8.5 builds on the foundation laid here.
::note{label="PHP 8.x series"}
This is part of a series on what's new in each modern PHP release.
Next: [What's New in PHP 8.1](https://franktheprogrammer.com/articles/whats-new-in-php-8-1)
::
# What's New in PHP 8.1
PHP 8.1 arrived on 25 November 2021, and it is the release that turned a lot of skeptics into fans. After 8.0 modernized the syntax, 8.1 filled in the features people had been faking for years: real enums, properties you cannot mutate, and a proper concurrency primitive. Here are the ones worth knowing.
## Enumerations
Before 8.1, a fixed set of options meant class constants or bare strings, with nothing stopping an invalid value from sneaking through. Enums give you a real type whose instances are the only legal values, and they can carry methods.
```php
enum OrderStatus: string
{
case Pending = 'pending';
case Paid = 'paid';
case Shipped = 'shipped';
case Canceled = 'canceled';
public function canCancel(): bool
{
return match ($this) {
self::Pending, self::Paid => true,
self::Shipped, self::Canceled => false,
};
}
}
$status = OrderStatus::from('paid');
$status->canCancel(); // true
```
A backed enum maps each case to a scalar, so `OrderStatus::from('paid')` validates and returns the case, while `tryFrom('bogus')` returns null instead of throwing. This is the type-safe version of the constants idea I covered in [class constant visibility](https://franktheprogrammer.com/articles/setting-visibility-for-your-class-constants-in-php), with behavior attached.
## Readonly properties
A property marked `readonly` can be written once, during initialization, and never again. It is the single cleanest way to build an immutable value object.
```php
final class Coordinates
{
public function __construct(
public readonly float $lat,
public readonly float $lng,
) {}
}
$home = new Coordinates(40.7128, -74.0060);
$home->lat = 41.0; // Error: Cannot modify readonly property
```
Combined with constructor promotion from 8.0, an immutable object is now a handful of lines, and any attempt to mutate it after construction fails loudly rather than corrupting state somewhere downstream.
## First-class callable syntax
Passing a function around used to mean string names or the clunky `Closure::fromCallable()`. The new `...` syntax produces a real closure from any function or method, with full type information.
```php
$lengths = array_map(strlen(...), ['php', 'is', 'fun']);
// [3, 2, 3]
$normalizer = $formatter->normalize(...);
$clean = array_map($normalizer, $rawValues);
```
`strlen(...)` means "the strlen function as a closure," and `$formatter->normalize(...)` captures a bound method. It is concise, it is analyzable by your IDE and static analysis tools, and it works anywhere a callable is expected.
## Fibers
Fibers are PHP 8.1's lightweight, cooperative concurrency primitive. A fiber can pause itself mid-execution and hand control back to the caller, then resume exactly where it left off. They are the engine behind modern async libraries, but the core idea is small enough to demo directly.
```php
$fiber = new Fiber(function (): void {
echo "fetching...\n";
$chunk = Fiber::suspend('need data');
echo "resumed with: $chunk\n";
});
$request = $fiber->start(); // prints "fetching..." then returns 'need data'
$fiber->resume('here it is'); // prints "resumed with: here it is"
```
`Fiber::suspend()` freezes the function and returns its argument to whoever called `start()` or `resume()`. You hand a value back in, and execution picks up from the suspension point. You will rarely write this by hand, but understanding it demystifies how async PHP frameworks pull off non-blocking I/O.
## The never return type
The `never` type documents that a function never returns normally: it always throws or exits. That tells both the engine and your tooling that any code after the call is unreachable.
```php
function abort(int $status, string $message): never
{
http_response_code($status);
echo $message;
exit;
}
$user = find($id) ?? abort(404, 'Not found');
```
Because the type system knows `abort()` cannot fall through, the analyzer is satisfied that `$user` is non-null afterward. It is a tiny annotation that makes guard clauses and redirect helpers type-check cleanly.
## New in initializers
You can now use `new` to create objects in default parameter values, attribute arguments, and other initializer positions. This makes optional dependencies expressible without a null-and-assign dance in the constructor body.
```php
final class ReportService
{
public function __construct(
private Logger $logger = new NullLogger(),
) {}
}
$service = new ReportService(); // uses the NullLogger default
```
If a caller injects a real logger, it wins; otherwise the harmless default is used. The constructor body stays empty, and the default is right there in the signature where you can see it.
PHP 8.1 is where the modern PHP toolkit really came together. Enums and readonly properties in particular changed how people model domains, and you will see both lean on the 8.0 features that came just before. Next up, 8.2 takes the immutability story further and tidies up the type system.
::note{label="PHP 8.x series"}
This is part of a series on what's new in each modern PHP release.
Previous: [What's New in PHP 8.0](https://franktheprogrammer.com/articles/whats-new-in-php-8-0) · Next: [What's New in PHP 8.2](https://franktheprogrammer.com/articles/whats-new-in-php-8-2)
::
# What's New in PHP 8.2
PHP 8.2 shipped on 8 December 2022. It is a quieter release than 8.1, more about refinement than headline features, but the refinements are good ones: whole classes can be readonly, the type system gained new building blocks, and a couple of long-standing rough edges around traits and security finally got sanded down. Here is what stands out.
## Readonly classes
PHP 8.1 gave us readonly properties. PHP 8.2 lets you mark an entire class readonly, which applies the modifier to every property at once. For a value object where nothing should ever change, this is the whole declaration.
```php
readonly class Money
{
public function __construct(
public int $amountCents,
public string $currency = 'USD',
) {}
}
$price = new Money(1999, 'EUR');
$price->amountCents = 0; // Error: Cannot modify readonly property
```
No need to repeat `readonly` on each property the way the [8.1 example](https://franktheprogrammer.com/articles/whats-new-in-php-8-1) did. The class declaration says it once, and the engine enforces it everywhere, including for any dynamic properties (which are also forbidden, as we will see below).
## Disjunctive normal form types
DNF types let you combine union (`|`) and intersection (`&`) types in a single declaration, using parentheses to group the intersections. This expresses constraints that neither alone could.
```php
interface Identifiable { public function id(): int; }
interface Timestamped { public function createdAt(): int; }
function audit((Identifiable&Timestamped)|null $entity): void
{
if ($entity === null) {
return;
}
log("#{$entity->id()} at {$entity->createdAt()}");
}
```
The signature reads as "either something that is both `Identifiable` and `Timestamped`, or null." Before 8.2 you could not mix the two operators, so a case like this meant giving up and falling back to a looser type.
## Standalone null, false, and true types
`null`, `false`, and `true` became types you can use on their own. They are most useful as precise return types for functions that, by contract, only ever yield one of those values.
```php
final class Cache
{
public function clear(): true
{
// ... always succeeds, or throws
return true;
}
public function missingKey(): null
{
return null;
}
}
```
A function returning `: false` or `: true` tells the caller far more than `: bool` does, and pairs well with union types when a method returns either a real value or a sentinel `false`.
## The new Random extension
PHP 8.2 introduced an object-oriented `Random` extension built around a `Randomizer` class and pluggable engines. It replaces the old global Mersenne Twister functions with something testable and explicit, and it bundles handy helpers.
```php
$randomizer = new \Random\Randomizer();
$roll = $randomizer->getInt(1, 6);
$shuffled = $randomizer->shuffleArray(['spades', 'hearts', 'clubs', 'diamonds']);
$winner = $randomizer->pickArrayKeys($players, 1)[0];
```
Because the engine is injectable, you can pass a seeded engine in a test and get deterministic "random" results, then swap in a cryptographically secure engine in production. No more global state quietly deciding your dice rolls.
## Constants in traits
Traits can now declare constants. Previously only properties and methods were allowed, which forced awkward workarounds when a trait's behavior depended on a fixed value. Now the constant lives with the code that uses it.
```php
trait HasHttpStatus
{
public const DEFAULT_STATUS = 200;
public function statusOr(int $given): int
{
return $given ?: self::DEFAULT_STATUS;
}
}
final class JsonResponse
{
use HasHttpStatus;
}
echo JsonResponse::DEFAULT_STATUS; // 200
```
The constant is accessed through the using class, keeping the trait self-contained instead of leaning on the consumer to define the value.
## Sensitive parameter redaction
Stack traces are great for debugging and terrible for secrets, because an exception thrown deep in a call stack can dump a password or API token straight into your logs. The `#[SensitiveParameter]` attribute tells PHP to redact that argument from traces.
```php
function authenticate(
string $username,
#[SensitiveParameter] string $password,
): bool {
throw new RuntimeException('auth service down');
}
```
When that exception bubbles up, the trace shows the username but replaces the password with `Object(SensitiveParameterValue)`. It is a one-line change that closes a genuinely common leak, with no impact on how the function behaves.
PHP 8.2 is the kind of release that makes the previous year's features feel finished: readonly graduated from properties to whole classes, the type system filled in its gaps, and a couple of papercuts around traits and logging got fixed. Next, 8.3 keeps the momentum with typed constants and some sharp tooling additions.
::note{label="PHP 8.x series"}
This is part of a series on what's new in each modern PHP release.
Previous: [What's New in PHP 8.1](https://franktheprogrammer.com/articles/whats-new-in-php-8-1) · Next: [What's New in PHP 8.3](https://franktheprogrammer.com/articles/whats-new-in-php-8-3)
::
# What's New in PHP 8.3
PHP 8.3 was released on 23 November 2023. There is no single blockbuster feature here, but it is a deeply practical release: it closes gaps in the type system, adds tooling that catches real mistakes, and ships a few standard-library functions you will wonder how you lived without. Here are the highlights.
## Typed class constants
Class constants can finally carry a type declaration. Before 8.3, a child class could redefine a constant with a completely different type and PHP would shrug. Now the type is part of the contract and is enforced.
```php
interface HasVersion
{
const string VERSION = '1.0';
}
class App implements HasVersion
{
const string VERSION = '8.3';
const int MAX_UPLOAD = 10;
}
```
If a subclass tried to override `VERSION` with an `int`, PHP would reject it at compile time. This finishes the work that promotions, readonly, and the standalone types from earlier releases started: putting real types on every member of a class.
## The Override attribute
`#[Override]` tells PHP "this method is meant to replace a parent method," and the engine verifies that a matching parent method actually exists. It catches the classic bug where a typo silently creates a brand-new method instead of overriding the one you intended.
```php
class TestCase
{
protected function tearDown(): void {}
}
class UserTest extends TestCase
{
#[Override]
protected function taerDown(): void // Fatal error: nothing to override
{
// cleanup that would never have run without the attribute
}
}
```
Without the attribute, that misspelled `taerDown()` would just sit there, never called, and your cleanup would quietly never run. With it, PHP refuses to compile until the name matches.
## json\_validate()
Checking whether a string is valid JSON used to mean calling `json_decode()` and inspecting the error, which wastes memory building a structure you are about to throw away. `json_validate()` answers the yes-or-no question without allocating the result.
```php
$payload = file_get_contents('php://input');
if (!json_validate($payload)) {
http_response_code(400);
exit('Invalid JSON');
}
$data = json_decode($payload, true);
```
For large payloads, or a hot path that rejects bad input early, this is meaningfully cheaper than decoding twice. It is a small function with a very clear job.
## Dynamic class constant fetch
You can now fetch a constant whose name is held in a variable using `Class::{$name}` syntax, the same way dynamic property and method access already worked.
```php
enum Suit: string
{
case Hearts = 'H';
}
class Settings
{
const string THEME_DARK = 'midnight';
const string THEME_LIGHT = 'daylight';
}
$choice = 'THEME_DARK';
echo Settings::{$choice}; // 'midnight'
```
Before 8.3 this meant reaching for the `constant()` function with a clumsy string of the fully qualified name. The new syntax is both shorter and consistent with the rest of the language.
## Random string generation
The `Random` extension from [PHP 8.2](https://franktheprogrammer.com/articles/whats-new-in-php-8-2) gained `getBytesFromString()`, which builds a random string from exactly the alphabet you give it. That makes generating tokens, codes, and slugs a one-liner.
```php
$randomizer = new \Random\Randomizer();
$alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
$token = $randomizer->getBytesFromString($alphabet, 12);
// e.g. "q3v9zk1mb7xa"
```
You control the character set, so there are no surprise symbols to URL-encode or confusing look-alike characters if you do not want them. PHP 8.3 also added `getFloat()` for unbiased random floats within an interval, rounding out the extension nicely.
## Readonly deep cloning
Readonly properties are write-once, which created a real problem for `__clone()`: you could not give a cloned object fresh copies of its nested objects, because reassigning the property was forbidden. PHP 8.3 allows readonly properties to be written exactly once inside `__clone()`.
```php
final class Order
{
public function __construct(
public readonly Address $shipTo,
) {}
public function __clone(): void
{
$this->shipTo = clone $this->shipTo; // now allowed in __clone
}
}
```
Cloning an `Order` now produces an independent `Address` rather than two orders quietly sharing one. This made readonly value objects practical to copy, which sets the stage for the much nicer cloning syntax that arrives in 8.5.
PHP 8.3 is the kind of release you upgrade to without drama and then quietly enjoy. The typed constants and `#[Override]` attribute prevent bugs, and the new functions remove little chores. Next, 8.4 brings one of the biggest syntax additions of the whole 8.x line: property hooks.
::note{label="PHP 8.x series"}
This is part of a series on what's new in each modern PHP release.
Previous: [What's New in PHP 8.2](https://franktheprogrammer.com/articles/whats-new-in-php-8-2) · Next: [What's New in PHP 8.4](https://franktheprogrammer.com/articles/whats-new-in-php-8-4)
::
# What's New in PHP 8.4
PHP 8.4 landed on 21 November 2024, and it carries the most significant syntax change since enums: property hooks. Alongside it came asymmetric visibility, a cleaner way to chain off `new`, some array functions people had been requesting for a decade, and a brand-new HTML5 DOM parser. This is a big one.
## Property hooks
Property hooks let a property compute its value on read or transform it on write, without you writing a separate getter and setter method. The property stays a property from the caller's point of view, but you control what happens behind it.
```php
final class Temperature
{
public function __construct(public float $celsius = 0.0) {}
public float $fahrenheit {
get => $this->celsius * 9 / 5 + 32;
set (float $value) => $this->celsius = ($value - 32) * 5 / 9;
}
}
$t = new Temperature(25);
echo $t->fahrenheit; // 77
$t->fahrenheit = 212;
echo $t->celsius; // 100
```
`$fahrenheit` stores nothing of its own. Reading it derives a value from `celsius`, and writing it updates `celsius` in turn. Callers just use `$t->fahrenheit` like any other property, with none of the getter/setter ceremony, and the hook is a natural home for validation or normalization too.
## Asymmetric visibility
Sometimes you want a property the whole world can read but only the class itself can change. Before 8.4 that meant a private property plus a public getter. Now you can say it in one declaration.
```php
final class ShoppingCart
{
public private(set) array $items = [];
public function add(Product $product): void
{
$this->items[] = $product;
}
}
$cart = new ShoppingCart();
echo count($cart->items); // reading is fine
$cart->items = []; // Error: cannot modify private(set) property
```
`public private(set)` means public to read, private to write. Outside code can inspect the cart's items but cannot reach in and replace them, so the only way to change the contents is through `add()`. I used this exact pattern in [SOLID Principles in Modern PHP](https://franktheprogrammer.com/articles/solid-principles-modern-php) to model a registry.
## new without parentheses
A small but delightful change: you can now call a method or access a property directly on a freshly constructed object without wrapping the `new` expression in parentheses.
```php
// Before 8.4
$name = (new ReflectionClass($model))->getShortName();
// PHP 8.4
$name = new ReflectionClass($model)->getShortName();
```
The extra parentheses were pure noise, and now they are gone. It reads exactly the way you would say it: make one of these, then call this on it.
## The array\_find family
PHP finally shipped the higher-order array functions that every other language has had forever. `array_find` returns the first matching element, while `array_any` and `array_all` answer boolean questions, all without writing a loop and a flag variable.
```php
$users = $repository->all();
$firstAdmin = array_find($users, fn (User $u) => $u->role === 'admin');
$hasAdmin = array_any($users, fn (User $u) => $u->role === 'admin');
$allActive = array_all($users, fn (User $u) => $u->isActive);
```
`array_find` returns the matching user or null, so it pairs naturally with the [nullsafe operator](https://franktheprogrammer.com/articles/whats-new-in-php-8-0). There is also `array_find_key` when you want the key instead of the value. Together they replace a surprising amount of boilerplate loop code.
## The Deprecated attribute
You can now mark your own functions, methods, and constants as deprecated with `#[Deprecated]`, and PHP will emit a deprecation notice whenever they are used. Until 8.4 this was only possible for internal PHP functions.
```php
final class PaymentGateway
{
#[Deprecated(message: 'use charge() instead', since: '2.5.0')]
public function makePayment(int $cents): void
{
$this->charge($cents);
}
public function charge(int $cents): void { /* ... */ }
}
```
Anyone calling `makePayment()` gets a deprecation warning pointing them at `charge()`, and static analysis tools surface it too. It is a clean way to guide consumers of your library off an old API without breaking them overnight.
## A modern HTML5 DOM
The old DOM extension predated HTML5 and mangled modern markup. PHP 8.4 added `Dom\HTMLDocument`, a spec-compliant parser with the `querySelector` API you already know from the browser.
```php
$dom = Dom\HTMLDocument::createFromString(
'
Hello
World
'
);
$title = $dom->querySelector('.title');
echo $title->textContent; // "Hello"
```
No more wrestling with `DOMDocument::loadHTML()` and its libxml warnings on perfectly valid HTML5. Scraping, transforming, or testing markup is now genuinely pleasant, with CSS selectors instead of XPath gymnastics.
PHP 8.4 is a landmark release. Property hooks and asymmetric visibility change how you design classes, and the smaller additions sand down chores you have lived with for years. The final stop in this series, 8.5, leans into composition with the pipe operator and a much nicer way to clone objects.
::note{label="PHP 8.x series"}
This is part of a series on what's new in each modern PHP release.
Previous: [What's New in PHP 8.3](https://franktheprogrammer.com/articles/whats-new-in-php-8-3) · Next: [What's New in PHP 8.5](https://franktheprogrammer.com/articles/whats-new-in-php-8-5)
::
# What's New in PHP 8.5
PHP 8.5 was released on 20 November 2025, closing out a remarkable run of yearly releases. Where 8.4 was about how you design classes, 8.5 is about how you compose and use them: a pipe operator for chaining, a clean way to clone-with-changes, and a built-in URI parser, among others. Here is the tour, and the end of our series.
## The pipe operator
The pipe operator `|>` takes the value on its left and feeds it as the argument to the callable on its right, so you can read a transformation as a top-to-bottom pipeline instead of an inside-out nest of function calls.
```php
$slug = ' Hello, Fellow PHP Devs! '
|> trim(...)
|> strtolower(...)
|> (fn (string $s) => preg_replace('/[^a-z0-9]+/', '-', $s))
|> (fn (string $s) => trim($s, '-'));
echo $slug; // "hello-fellow-php-devs"
```
Each step gets the previous step's output. Compare that to `trim(preg_replace(..., strtolower(trim($input))))`, which you have to read from the inside out. The right-hand side is any callable, so the [first-class callable syntax](https://franktheprogrammer.com/articles/whats-new-in-php-8-1) from 8.1 and short closures slot right in.
## Cloning with property updates
PHP 8.3 made it possible to deep-clone readonly objects inside `__clone()`. PHP 8.5 goes further: `clone()` now takes an array of property values to overwrite on the copy, which makes the immutable "with-er" pattern a single expression.
```php
final class Color
{
public function __construct(
public readonly int $r,
public readonly int $g,
public readonly int $b,
public readonly int $alpha = 255,
) {}
public function withAlpha(int $alpha): static
{
return clone($this, ['alpha' => $alpha]);
}
}
$blue = new Color(0, 0, 255);
$ghost = $blue->withAlpha(64); // a new Color, original untouched
```
The original object is never mutated; you get a fresh copy with just the named properties changed. This is exactly the immutable update that used to require a full constructor call repeating every unchanged field.
## The NoDiscard attribute
Some return values are too important to ignore: the boolean that says whether a write succeeded, the new immutable object from a with-er. `#[NoDiscard]` makes PHP warn when a call's return value is thrown away.
```php
final class Config
{
#[NoDiscard('the returned Config is the updated one; this is immutable')]
public function withDebug(bool $on): static
{
return clone($this, ['debug' => $on]);
}
}
$config->withDebug(true); // Warning: return value not used
$config = $config->withDebug(true); // correct
(void) $config->withDebug(true); // explicitly discard, no warning
```
It catches the very common mistake of calling an immutable method and forgetting to capture its result, which otherwise does nothing at all. If you really mean to discard the value, casting to `(void)` says so and silences the warning.
## array\_first and array\_last
PHP 7.3 gave us `array_key_first()` and `array_key_last()`. PHP 8.5 completes the set with `array_first()` and `array_last()`, which return the first and last values directly.
```php
$events = $log->recent();
$latest = array_last($events); // last value, or null if empty
$oldest = array_first($events); // first value, or null if empty
```
No more `$arr[array_key_last($arr)]` or `end($arr)` with its internal-pointer side effects. They return null on an empty array, so they pair naturally with the null coalescing operator and the [array\_find family](https://franktheprogrammer.com/articles/whats-new-in-php-8-4) from 8.4.
## The URI extension
Parsing URLs in PHP has long meant `parse_url()`, which is loose about edge cases and not standards-compliant. PHP 8.5 ships a proper URI extension with classes that follow RFC 3986 and the WHATWG URL standard.
```php
use Uri\Rfc3986\Uri;
$uri = new Uri('https://example.com:8080/blog/post?ref=newsletter#top');
$uri->getScheme(); // "https"
$uri->getHost(); // "example.com"
$uri->getPort(); // 8080
$uri->getPath(); // "/blog/post"
$uri->getQuery(); // "ref=newsletter"
```
You get real parsing, normalization, and validation instead of a best-effort associative array. For anything that handles user-supplied links, that correctness matters: it is the difference between rejecting a malformed URL and quietly mishandling it.
## Backtraces on fatal errors
A long-standing debugging annoyance: fatal errors printed a message but no stack trace, so you knew what broke but not how you got there. PHP 8.5 includes a backtrace with fatal errors by default.
```ini
; enabled by default in PHP 8.5
fatal_error_backtraces = On
```
Now a fatal error shows the call chain that led to it, the same way an uncaught exception always has. It is a quiet change, but the first time a production fatal hands you a full trace instead of a single cryptic line, you will appreciate it.
That wraps our walk from PHP 8.0 to 8.5. Step back and the arc is clear: 8.0 modernized the syntax, 8.1 and 8.2 made immutability and types first-class, 8.3 and 8.4 reshaped how you design classes, and 8.5 makes composing them a pleasure. If you started this series on the [8.0 article](https://franktheprogrammer.com/articles/whats-new-in-php-8-0), the contrast between then and now is its own little lesson in how far the language has come.
::note{label="PHP 8.x series"}
This is the final part of a series on what's new in each modern PHP release.
Previous: [What's New in PHP 8.4](https://franktheprogrammer.com/articles/whats-new-in-php-8-4)
::
# Yarn: Fast and Secure Dependency Management
Yarn is a super simple dependency management tool which acts as a drop-in replacement for npm, so you can get started using it right away. The best way to install yarn is by using npm. That's right, you use npm to install yarn, npm's replacement essentially, how fun is that!
::note{label="From 2026"}
When this was written, Yarn was notably faster and more secure than npm. Since then npm has caught up: it uses a lockfile, caches packages, and verifies integrity by default, so the "faster and more secure" framing is dated. Both are solid choices today, and Yarn now offers a modern Berry line (Yarn 2+ with Plug'n'Play). Pick whichever your team prefers.
::
There are a few great reasons as to why you might start using yarn today.
- Yarn sets up a lock file with the exact versions being used within a project. This improves the speed of installation dramatically and also allows you to share your lock file with other developers to ensure that everyone involved is using the same version numbers of a given package.
- Yarn also caches every package it downloads locally, this way you never need to download the same package twice.
- Yarn also installs packages in parallel improving the speed in which projects are set up.
- Yarn also uses checksums to install packages, making it super secure and validating the packages before any code is run within your codebase.
## Installation
All of the following installation instructions are available on the yarn documentation. I have included the information below for ease of reference.
### via npm
My preferred method, if you have npm already installed on your system, is to simply run the following command to get yarn easily installed.
```bash
npm install --global yarn
```
There are plenty of other options to get it installed. I have included the link below to the yarn documentation for you to pick the option that works best for you.
[Yarn Installation Documentation](https://yarnpkg.com/en/docs/install){rel=""nofollow""}
## Usage within a project
Now that we have everything installed, we need to start configuring our project to use yarn. Once within your project root directory, you'll want to run the following command.
```bash
yarn init
```
If you already have a yarn.lock file within your project, or if you have a package.json file you can run the following command.
```bash
yarn install
```
Or you could simply just type
```bash
yarn
```
### Adding new packages/modules
Installing packages is super simple now that you have yarn configured. The quickest and easiest way is by using the following.
```bash
yarn add [package]
```
If you want to add a specific version to your project instead of using the latest version of a package you'd run the following.
```bash
yarn add [package]@[version]
```
You can also specify a specific tagged version of a package by running the following command.
```bash
yarn add [package]@[tag]
```
### Deleting a Package
Deleting/Removing a package could not be any easier. Simply run the following command and yarn will take care of the rest for you.
```bash
yarn remove [package]
```
### Updating/Upgrading a Package
To upgrade an existing package to the latest version you'd just need to run the following command.
```bash
yarn upgrade [package]
```
You can also specify what version to update to by running the following command.
```bash
yarn upgrade [package]@[version]
```
If you want to upgrade a specific tagged version, then you could run the following as well.
```bash
yarn upgrade [package]@[tag]
```
# Yarn: Publishing a Package
Publishing a package to the npm repository has never been simpler. With a few steps you can create a package that is redistributable to all of your projects.
If you do not have yarn already configured, [click here for setup instructions](https://franktheprogrammer.com/articles/yarn-fast-and-secure-dependency-management).
One thing to note when publishing a package. Once you publish a package at a specific version, you can never change that version again.
## Publishing a Package
Within your projects root directory you'll have a package.json file which will define all of the details regarding your package. Within that directory you'd run the following command to publish your package.
```bash
yarn publish
```
You'll be prompted for your username and password for [npmjs.com](http://npmjs.com/){rel=""nofollow""}. If you do not already have an account, be sure to create your account by [Clicking Here](https://www.npmjs.com/signup){rel=""nofollow""}.
If you have a gzipped tarball .tgz of your package that you want to publish instead you can run the following command.
```bash
yarn publish [tarball]
```
If you're outside of your projects root directory, you can define the folder you wish to publish as so.
```bash
yarn publish [folder]
```
You'll want to make sure that the folder you're publishing contains a package.json file within the root of the directory.
If you want to publish a specific tag of your package, then use the following command within the project's root directory to specify the tag.
```bash
yarn publish --tag
```
You can also control the access within the npm registry to a package you're publishing by defining the access flag as so.
```bash
yarn publish --access
```
## Unpublish A Package From The Repository
You'll want to be careful with this command, as if anyone is using your package you can cause issues for them. To remove a package from the [npmjs.com](http://npmjs.com/){rel=""nofollow""} repository simply run the following.
```bash
npm unpublish --force
```
At the time of this writing, there does not seem to be a way that I'm familiar with to unpublish a package via yarn so the traditional npm method will suffice.
Before you are able to use this command you will also need to log in to [npmjs.com](http://npmjs.com/){rel=""nofollow""} via the command line by running npm login within your project's root directory.
## Example Package
First we'll start by creating a blank directory named logger.
Within the directory, let's start by running the following command which will create our package.json file.
```bash
yarn init
```
This command will take you through a series of questions and then output a file package.json with the following contents.
```json
{
"name": "example-logger",
"version": "1.0.0",
"description": "Logs a message to the console.",
"main": "index.js",
"author": "Frank Perez ",
"license": "MIT"
}
```
Your file will contain slightly different information, depending on how you answered each of the prompts previously. Keep in mind that the name should also be unique to what you find in [npmjs.com](http://npmjs.com/){rel=""nofollow""}, if not your package will fail when attempting to publish.
We now have our package.json file and listed that our main file would be named index.js. Let's now create the file and put in the code we want for the package.
Here are the contents for my index.js file.
```javascript
exports.logger = function() {
console.log('Hello from our Logger Package.');
}
```
Once you have your files setup, you can now run the command yarn publish within your projects root directory. Shortly after it finishes, you will get a confirmation message letting you know that your package was published successfully.
Now to install your newly created package you'd simply run the following command within your future projects.
```bash
yarn add example-logger
```