php shorthand if

I’ve known about the shorthand if for years:

<?php echo ($username) ? $username : ''; ?>

Today If saw this which is called Ternary and has been available since 5.3 which is older than my days of coding….

<?php echo ($username) ?: ''; ?>

Update PHP7.4
When assigning value especially in an array or object you can now do the following.

<?php

$person = [ 'date_of_birth' => '1970-01-01' ];

$person['name'] = $person['name'] ?? 'John Doe'; //php7.3
$person['name'] ??= 'John Doe';                  //php7.4

var_dump( $person );

for & foreach loops performance

Today we will handle a case of “Premature Optimization Is the Root of All Evil“.
But this is my blog and I was working with a very big api set, and will only get bigger, so (premature) thinking about memory usage and execution time might be a good idea in the long run.

Before I started this I thought that a for loop was faster then a foreach loop. And I usually pick foreach because it’s easier to write and read.

A quick google lands on a stackoverflow question which concludes the opposite. So I started to test a bit.

There is a big difference in my use case here.

I need to remove array items that need to be excluded from the api results. Most examples you will find online are about editing items.

First tests where quite clear, using a foreach was in most configurations faster than for. The test array I create has 10000 items and every 3rth item should be excluded:

<?php
$test_array = array();
for ( $i = 0; $i <= 10000; $i ++ ) {
    $test_array[] = [
        'index'   => $i,
        'include' => ( $i % 3 === 0 ) ? true : false,
    ];
}

The traditional foreach loop

The way I have been filtering arrays for years.
Create a empty array and only put in the elements the need to be included.

<?php
$filtered_array = [];
foreach ( $test_array as $item ) {
    if ( true === $item['include'] ) {
        $filtered_array[] = $item;
    }
}
return $filtered_array;

Immediately delete item

Don’t use a between array, just unset the item in the parent array

<?php
foreach ( $test_array as $key => $item ) {
    if ( true === $item['include'] ) {
        unset( $test_array[ $key ] );
    }
}
return $test_array;

Traditional pass by reference

The same as before, but the $item is passed by reference.
This is the big difference, since we are not editing the $item we want to remove it from the parent array

<?php
$filtered_array = [];
foreach ( $test_array as &$item ) {
    if ( true === $item['include'] ) {
        $filtered_array[] = $item;
    }
}
return $filtered_array;

Immediately delete pass by reference

Again the same and again passed by reference

<?php
foreach ( $test_array as $key => &$item ) {
    if ( true === $item['include'] ) {
        unset( $test_array[ $key ] );
    }
}
return $test_array;

The for loop

And last the for loop I was wondering about

<?php
$length = count( $test_array );
for ( $i = 0; $i < $length; $i ++ ) {
    if ( false === $test_array[ $i ]['include'] ) {
        unset( $test_array[ $i ] );
    }
}
return $test_array;

the results

I ran each of these loops 5000 times and measured the total time that took.
This was to insure the time between results was big enough to exclude the randomness (at least enough)
The test code I ran

Speed of Loops
  1. 5.6122910976414sec Loop: foreach traditional
  2. 6.0467801094055sec Loop: foreach unset key
  3. 7.7878839969635sec Loop: foreach traditional pass by reference
  4. 7.0686309337616sec Loop: foreach unset key pass by reference
  5. 8.6388339996338sec Loop: for

The traditional foreach I’ve been using for years turned out to be the fastest.
Research hours well spend ?

Bonus edit array item

As said before most examples use editing a array.
So I also ran that scenario. Test code
I upped the loops from 5000 runs to 7500 because the difference was so small.
And still it’s close.

  1. 15.363855123523sec Loop: foreach traditional
  2. 10.987272024155sec Loop: foreach foreach edit array directly
  3. 11.358484983444sec Loop: foreach traditional by reference
  4. 14.363346099854sec Loop: for

Here the traditional is the slowest. Reference is a lot faster as most articles claim.
But editing the array directly was the fastest.

WordPress filters and anonymous functions

Anonymous functions have been around for a long time. And since WordPress now supports php 5.6 it can be safely used.
And appears to be allowed?

Personally I’m not a fan of anonymous functions in combination with WordPress actions and filters. My main concern is you can’t remove them once registered. How ever today I found a use case which was very usefull in combination with the use

My example:

<?php
/* Template name: some-template */

// Gather all data
$condition_for_title = true;
$h1_title_override = 'very heavy and complicated check';
// the H1 also needed as the <title>

add_filter( 'pre_get_document_title', function( $title ) use ($h1_title_override, $condition_for_title) {
    if ($condition_for_title) {
        return $h1_title_override;
    }
    return $title;
}, 20, 1 );

get_header();

// start body
?>
    <h1><?php echo $h1_title_override ?>

Here I pass 2 variables h1_title_override and $condition_for_title which are created outside the function. In my case these where quite complicated and heavy checks. Of course I could put those in a function and cache the result. And call that check in the filter function. But still I need to check the current template before doing the function.

More traditional Example:

in functions.php

<?php
function complicated_check() {
    // Gather all data
    $condition_for_title = true;
    $h1_title_override   = 'very heavy and complicated check';

    return [
        'condition_for_title' => $condition_for_title,
        'h1_title_override'   => $h1_title_override,
    ];
}

function title_exception_for_template( $title ) {
    if ( ! is_page_template('clean-template.php')) {
        return $title;
    }

    $template_data = complicated_check();

    if ( $template_data['condition_for_title'] ) {
        return $template_data['h1_title_override'];
    }

    return $title;
}

add_filter( 'pre_get_document_title', 'title_exception_for_template', 20, 1 );

in clean-template.php

<?php
/* Template name: clean-template */
$template_data = complicated_check();

get_header();

// start body
?>
    <h1><?php echo $template_data['h1_title_override'] ?>

Both these approaches do the same thing. But the more traditional way is a lot more code. Although it has cleaner template.
I probably won’t use this much. If the anonymous function was more complicated it will get hard to read.

But for this case I think it was neat that I could use this little feature.

Installing selfoss on Raspberry Pi

Selfoss is my preferred RSS reader. I use it daily to follow many sites.
In this guide we are going to install it and configure it using Nginx.

Selfoss is my preferred RSS reader. I use it daily to follow many sites.
In this guide we are going to install it and configure it using Nginx.

Preparing the vhost in Nginx

In this example I’m using selfoss.local as the url to set it up. Change the url to whatever you want.

sudo mkdir -p /var/www/selfoss.local/public_html
cd /var/www/selfoss.local/public_html

Create the vhost file: sudo nano /etc/nginx/sites-enabled/selfoss.local.conf

server {
    listen 80;

    server_name selfoss.local;
    root /var/www/selfoss.local/public_html;

    index index.php index.html;

    location ~*  (gif|jpg|png) {
        expires 30d;
    }

    location ~ ^/(favicons|thumbnails)/.*$ {
        try_files $uri /data/$uri;
    }

    location ~* ^/(data/logs|data/sqlite|config.ini|.ht) {
        deny all;
    }

    location / {
        index index.php;
        try_files $uri /public/$uri /index.php$is_args$args;
    }

    location ~ .php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
        fastcgi_intercept_errors on;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
    }
}

And finally reload nginx: sudo nginx -t && sudo service nginx reload

Download Selfoss

Next we download Selfoss. On the Selfoss home page there is a download button. Copy the destination of that link.
At the time of writing it is on version 2.18 and the download zip is
Replace the url if needed.

sudo wget https://github.com/SSilence/selfoss/releases/download/2.18/selfoss-2.18.zip -O selfoss.zip
sudo unzip selfoss.zip
sudo rm selfoss.zip
sudo chown www-data:www-data /var/www/selfoss.local -R
Screenshot of selfoss empty installation

Now visit the url. You should see an empty -but working- selfoss screen.

Configuration

Although this installation works we are going co configure some aspects of it.

DB connection.

Be default Selfoss uses sqlite. Mysql has a better performance. Instructions to install Mysql are in a separate article.
More detailed instruction are there. So here are the quick instructions to create a database.

Login to mysql as root: sudo mysql -uroot
Create a db, and assign a new user. !Replace the password
You can set your own database name and user.

CREATE DATABASE misc_selfoss;
CREATE USER 'selfoss'@'localhost' IDENTIFIED BY '%%SAFE_PASSWORD%%';
GRANT ALL PRIVILEGES ON `misc_selfoss`.* TO `selfoss`@`localhost`;
FLUSH PRIVILEGES;
EXIT;
Creating the database for Selfoss

Next we set the db connection in the config file: sudo nano config.ini

[globals]
db_type=mysql
db_file=data/sqlite/selfoss.db
db_host=localhost
db_database=misc_feeds
db_username=janwfeeds
db_password=%%SAFE_PASSWORD%%
db_port=
db_prefix=self_

A few cleanup commands afterwards

sudo rm data/sqlite/selfoss.db
sudo chown www-data:www-data /var/www/selfoss.local -R

Password protection

To protect your selfoss installation we will set a password.
Go to your url/password in my case: http://selfoss.local/password

Screenshot of the Seelfoss password generation screen

After go got the hash add it to the config file: sudo nano config.ini

username=yourusername
password=a19fe3f69ec64cded61d0dbc01f0200ab265df0c3a2828d0e53d47bc93032698fbc7862e536c324f47edd6bd1f3e07571cca898fae140394f93632fa9334ad

Other configurations.

There are more configurations. See the official documentation Personally I prefer that items get marked as read when I open them.

auto_mark_as_read=1
homepage=unread

Now you are ready to start using Selfoss. Have fun!

WP-cli run command over each subsite

If you use WP-cli command on a multisite it be default will only run on the mainsite.
But often you want to change a setting for all the sites.
In my case I wanted to set the timezone to Amsterdam for the whole network. That’s not hard:

wp option set timezone_string 'Europe/Amsterdam'

On a multisite this is a bit more difficult. But the script below will do the same for each site in a multisite.

wp site list --field=url | xargs -I % sh -c 'printf "SITE: %n"; wp option set timezone_string 'Europe/Amsterdam' --url=%'

It consists of 3 parts.
First create a list of all site url’s

wp site list --field=url

Secondly we pass that on to xargs.
xargs is a very powerfull tool. One that I hardly understand and should go into deeper one day.
This is the best tutorial I found if you want to start with xargs.

The only thing important now is the -I %. This sets the variable to %.
But the most important thing here is that inside the '***' You can run any command. like normal.

xargs -I % sh -c '***'

Which brings us to the final part.
First print the site url on a line, then do the actual command we want to do on each sub site. As you can see we pass on the --url=% where we set the variable given in xargs.

printf "SITE: %n"; wp option set timezone_string 'Europe/Amsterdam' --url=%

VVV disable backups

Update VVV 3.0

As of the release of VVV 3.0 this can all be done by adding the following in the vvv-custom.yml

general:
  # Backup the databases to the database/backups subfolder on halt/suspend/destroy, set to false to disable
  db_backup: false
  # Import the databases if they're missing from backups
  db_restore: false
list of vvv custom action files

Pre VVV 3.0

VVV is great but if you have 20+ sites in it most of which are quite big doing a reload or halt can be quite slow.
Disabling it isn’t the easiest. In vagrant-root/config/homebin/ create these 3 files:

  • vagrant_destroy_custom
  • vagrant_halt_custom
  • vagrant_suspend_custom

I wish there was a way to easily disable these. The vvv-custom.yml would be great for this.
Also adding a way to exclude specific databases.

source

php array; insert new item at specific index

The php function array_splice can be used to insert new items. At specific places.

<?php
$breadcrumbs = [
    'home',
    'year',
    'month',
    'day',
];
$new_crumb = [
    'category'
];
array_splice($breadcrumbs, 1, 0, $new_crumb);

The result is that category is inserted at the second place.

<?php
array (
    0 => 'home',
    1 => 'category',
    2 => 'year',
    3 => 'month',
    4 => 'day',
);

Running php from command line

This is one of those “I know this is possible, but don’t know how”.

php -r '$parts = [ "hello", "world" ]; echo implode( " ", $parts ) . PHP_EOL;' # hello world

Only useful for simple onelines. You’re very likely to be better of putting code in a php file and run that script like:

php ./helloworld.php

Run PHP with WordPress loaded.

Of course your good old friend wp-cli can help.
It can run code with wp fully loaded. So if you add things in the init action or even after the wp_loaded, those plugin/theme functions, posttypes and such are all available.

First off the plain php code execution, with wp eval

wp eval 'var_dump(did_action( "wp_loaded" ));'  # int(1)

Secondly we execute a file with WordPress fully loaded using wp eval-file

wp eval-file ./helloworld.php

Installing PHP7.4 on Rapsberry Pi

In this guide we will install php 7.4 and do very basic tweaks. This should also work with php8 and older versions.

In this guide we will install php 7.4 and do very basic tweaks

Note: this post call also be used to install php7.0, php7.1, php7.2 or php7.3, just change every reference to 7.x.
Only php7.3 and higher are still supported
This guide should also work for installing php8.0 but I haven’t tested this yet.

Check if you can install php7.4

To check if php7.4 is available run: sudo apt install --dry-run php7.4
If you get an error continue, if you don’t get an error continue to actual install php

As the php version is not available
We are going to create a new source for php. We do this with the following 3 commands.

sudo wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
echo "deb https://packages.sury.org/php/ buster main" | sudo tee /etc/apt/sources.list.d/php.list
sudo apt update

Actual install php

Now we are ready to install php7.4 with all modules we need:

sudo apt install -y php7.4-common php7.4-fpm php7.4-cli php7.4-curl php7.4-intl php7.4-json php7.4-mysql php7.4-opcache php7.4-gd php7.4-sqlite3 php7.4-mbstring php7.4-zip php7.4-readline php-pear imagemagick php-imagick

When done check it with php -v it should show a php 7.4.0 or higher.
Now we need to add a few fpm things for nginx to work properly.
Create a extra config file sudo nano /etc/php/7.4/fpm/conf.d/90-pi-custom.ini.
And add:

cgi.fix_pathinfo=0

upload_max_filesize=64m
post_max_size=64m
max_execution_time=600

Finally reload php:

sudo service php7.4-fpm reload

Now PHP is ready to use.

To check configuration run php -i To see the phpinfo in the browser check the nginx guide, as php needs to be connected to nginx.

Extra’s

If you’re interested in installing some php applications you might want to follow these other guides. to install.

Installing Nginx on Rapsberry Pi

In this guide we are going to install Nginx. Configure it to work with php and give Nginx it’s own users so you don’t have to update permissions.

In this guide we are going to install a webserver (nginx). For those who are bit familiar with webservers you might wonder why we are not using apache? I myself are more familiar with Apache but Nginx just has better performance. And on the not so powerful pi that is important.

Install Nxing

To start we just plainly install nginx.

sudo apt install -y nginx

Check the version nginx -v it should be version nginx/1.14.2 or higher

To test if it works enter the IP address in the browser. You should see this page.

If you see an error most likely IPv6 isn’t supported. Open the nginx config file sudo nano /etc/nginx/sites-enabled/default.
At the top you should see these lines

    listen 80 default_server;
    listen [::]:80 default_server;

Change them to:

    listen 80 default_server;
    #listen [::]:80 default_server;

Save then restart nginx.

sudo nginx -t && sudo service nginx reload

Then test the page again in the browser.

Setting up a site with php and url

First we will do some global settings open: sudo nano /etc/nginx/conf.d/90-pi-custom.conf
Add the following line, we increase the allowed upload sizes.

client_max_body_size 64M;

Note: for the example I use example.local replace that with the url you want to use. If you don’t have the url setup yet you can add it to you own host file. Or instead of the url use the IP-address of the Pi.

We will create our own vhost files. PS I’ll be using example.local but use whatever you intend to use. Create the vhost file: sudo nano /etc/nginx/sites-enabled/example.local.conf

server {
    listen [::]:80;
    listen 80;

    server_name example.local;
    root /var/www/example.local/public_html;

    index index.php index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ .php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        fastcgi_intercept_errors on;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
    }
}

Next create the folders needed. Set the rights of those files and add a test page.

sudo mkdir -p /var/www/example.local/public_html
sudo touch /var/www/example.local/public_html/index.php
sudo chown www-data:www-data /var/www/example.local -R
sudo nginx -t && sudo service nginx reload

In the main file add sudo nano /var/www/example.local/public_html/index.php

<?php
phpinfo();

To test the url example.local you need to add it to your host file. Depending if you’re on Windows, Mac or Linux the instructions are different. See this guide: How to edit your hostfile

Now go the the url and you should see this:

phpinfo dump on a working example.local
example phpinfo

Now we have a basic and functioning webserver with php.
For security sake delete the index.php file

sudo rm /var/www/example.local/public_html/index.php

Creating a new user for Nginx

So far we use sudo for pretty much everything. This is fine.
After the initial setup most of the work has to be done in /var/www/*. These files are owned by the www-data user. This is fine but it can be a bit annoying when editing files with a different user.
Either the owner is the www-data user or it’s the user you use to login in (like the pi user).


So we’re going to create a new user assign it as the nginx user. Then we’lluse that user going onward to login as that user when we’ll want to change things to the sites.

We will not change it to the pi or ubuntu, because those users have sudo access. That’s a huge security risk.

Create a new user

Before we create the user we are going to check the current users to see if it does not already exists.

cat /etc/passwd

With that clear let’s create the user:

sudo adduser deployment

Fill in the password, the rest of the questions are optional.

Assign this user to Nginx

In /etc/nginx/nginx.conf on line #1 change the user.

#user www-data;
user deployment;

And change the user for php-fpm (If you’re using php-fpm ?)

Create a file in /etc/php/7.4/fpm/pool.d/ (check the php version)
I suggest something like zzz-custom-user.conf so it’s loaded last. To that file add.

user = deployment
group = deployment
listen.owner = deployment
listen.group = deployment

Next reload everything, agian check php version.

sudo nginx -t && sudo service php7.4-fpm restart && sudo service nginx restart

Finally change the owner of the webfiles

sudo chown deployment:deployment /var/www/ -R

You should be done changeing the nginx user to deployment. Open a new terminal and try to login to your Pi using this newly created user: ssh deployment@192.168.2.111 It will ask for the password, but you should be able to login. If you want you can set you ssh key for the deployment user.

Also keep in mind if you had cronjobs on the previous user you should also move these to the new user. (We haven’t setup cronjobs in this guide).

You’re Nginx setup is now done and you are ready to add sites to it.