Posts Tagged ‘PHP’

Upgrading LAC to Slim 4

Wednesday, October 28th, 2020 | Programming

Last month, I published an upgrade guide to Slim 4 covering the changes that most people will need to make.

I recently upgraded the Leeds Anxiety Clinic and this project has some further complexities, so this post will elaborate on all of the changes I made, in case my original blog post did not cover some of the issues that you are also running into.

Things to do in advance

The old service layer calls no longer work:

$db = $this->ci->db;

And need to be updated to:

$db = $this->ci->get('db');

However, Slim 3’s service container already supports this syntax, so you can go ahead and update your code in advance.

If you are passing the container into controller functions, you need to typecast it.

use Psr\Container\ContainerInterface;

class Controller {
    __construct(ContainerInterface $ci) {}
}

If you pull the request out from the service container, you cannot do that in Slim 4. For example, if you have a render helper that relies on the request being in the container, you will have to start manually passing that into your render help from the page route closure.

Dependencies

You will want all of these:

"slim/slim": "4.*",
"slim/psr7": "1.*",
"slim/http": "1.*",
"php-di/php-di": "^6.1",

I did wonder if I could remove my other PSR-7 library that I use for Mailgun, nyholm/psr7, but the answer is no.

Instanciating the app

We now create the container and then the app, and you can recursively pass in the container.

$container = new \DI\Container;

$container->set('serviceA', function() use ($container) {
    return new ServiceA($container->get('serviceB'));
});

\Slim\Factory\AppFactory::setContainer($container);
$app = \Slim\Factory\AppFactory::create();

Throwing not found exceptions

If you manually throw not found exceptions or other HTTP exceptions, Slim 3 has them located here:

Slim\Exception\NotFoundException

Slim 4 moves it to here:

Slim\Exception\HttpNotFoundException

Error handling

If you want Slim 4’s default error handling, you need:

$errorMiddleware = $app->addErrorMiddleware(true, true, true);

And if you want to handle HTTP errors (such as not found), you can use:

$errorMiddleware->setErrorHandler(
    Slim\Exception\HttpNotFoundException::class,
    function (Psr\Http\Message\ServerRequestInterface $request) use ($container) {
        $controller = new App\Controller\ExceptionController($container);
        return $controller->notFound($request);
    });

You can also set your own error handling:

$errorMiddleware->setDefaultErrorHandler(
        function (Psr\Http\Message\ServerRequestInterface $request,
        Throwable $exception) use ($container) {
            $controller = new App\Controller\ExceptionController($container);
            return $controller->error($request, $exception);
        });

This means you will need to create your own response object in any exception controller you use.

public function notFound(Request $request)
{
    $response = new \Slim\Psr7\Response;
    return $this->render($response, 'not-found.html');
}

Custom middleware

The way we pass through middleware has changed. The old way:

public function __invoke($request, $response, $next)
{
    $response = $next($request, $response);
    return $response;
}

The new way:

public function __invoke($request, $handler)
{
    return $handler->handle($request);
}

That change alone probably won’t do what you want it to do out-of-the-box, so you might need to do some reading up and adapt it to your specific middleware depending on function.

Custom middleware wants to go towards the bottom after you define your routes, but before the error middleware.

Upgrading from Slim 3 to Slim 4

Friday, September 18th, 2020 | Programming

Slim is one of my favourite PHP microframeworks and many websites are built on Slim 3. I have recently moved to Slim 4 and thought I would document the upgrade process for anyone else looking to make the leap.

Dependencies

Slim 3 came with its own dependency injection container. This is no longer the case. Therefore, we need to bring one in, such as php-di that Slim supports out-of-the-box. We will also need a PSR-7 library and the Slim HTTP helpers.

"slim/slim": "4.*",
"slim/psr7": "1.*",
"slim/http": "1.*",
"php-di/php-di": "^6.1",

Creating the app

In Slim 3, we created the app and then accessed the container.

$app = new \Slim\App([
    'settings' => []
]);

$container = $app->getContainer();

In Slim 4, we create the container then attached it to the app.

$container = new \DI\Container();
\Slim\Factory\AppFactory::setContainer($container);
$app = \Slim\Factory\AppFactory::create();

You can still access the container in the old way and inject dependencies into it, but you need to manually pass it into the app as above.

Accessing dependencies

Now we are using our PSR-11 DI of choice, we need to change how we access our dependencies. Previously, wee could use the Slim shorthand.

$db = $this->ci->db;

Now we need to use the PSR-11 standard.

$db = $this->ci->get('db');

Exception handling

Previously, we could set error handling using the dependency injection container.

$container['notFoundHandler'] = function ($container) {
    return function ($request, $response) use ($container) {
        $controller = new \App\Controller\ExceptionController($container);
        return $controller->notFound($request, $response);
    };
};

In Slim 4, we need to use the bundled middleware.

$errorMiddleware = $app->addErrorMiddleware(true, true, true);

$errorMiddleware->setErrorHandler(
    Slim\Exception\HttpNotFoundException::class,
    function (Psr\Http\Message\ServerRequestInterface $request) use ($container) {
        $controller = new App\Controller\ExceptionController($container);
        return $controller->notFound($request);
    });

This also means changing the exception controller, too, if you use one. In Slim 3, we would still get a response object passed in.

    public function notFound(Request $request, Response $response)
    {
        return $this->render($response, 'not-found.html');
    }

With Slim 4, we need to create our own.

    public function notFound(Request $request)
    {
        $response = new \Slim\Psr7\Response;
        return $this->render($response, 'not-found.html');
    }

That should take care of most of the changes. If you run into anything else, let me know, and I’ll update this article.

Slim Framework course

Saturday, July 25th, 2020 | News

Learn Slim microframework with my new course. You will build your first six projects including a searchable music catalogue, e-commerce project listings and member login and authentication system. Watch the course trailer below or preview the course.

Doctrine ORM course

Monday, June 8th, 2020 | News, Programming

My new course on Doctrine ORM is now available. If you are a PHP developer, adding Doctrine to your CV is a much=sought-after skill to have, being used by Symfony and thousands of other projects.

Here’s the trailer:

Slim PHP 3.12.3

Friday, December 6th, 2019 | Programming

If you’re using the Slim framework in PHP, there is a breaking change between 3.12.2 and 3.12.3. You’ll need to change where you are inheriting your class from.

- use Interop\Container\ContainerInterface;
+ use Psr\Container\ContainerInterface;

Rauma 4.0 released

Monday, February 4th, 2019 | Programming

I’m pleased to announce the release of the next major version of Rauma, 4.0.

Rauma is a full-stack PHP framework that gives you database, templating, session, authentication and many other functions out of the box. It’s the framework behind many of Worfolk Online’s websites.

Not much as changed in the 4.0 release, but it gets a major version bump because it’s a breaking change. Here’s what you need to know:

Authentication has been overhauled. The auth service now includes an isLoggedIn function to be a bit more verbose than checking for data. More importantly, you can now extend the base Authorisation class and create your own. This allows you to cache more data, connect to other services and implement persistent logins.

We’ve also deprecated the user description field, in favour of the new attributes feature that was added in version 3.6.

Two other things to be aware of:

There are now a set of proxy objects for things like JsonResponse so that you don’t have to import them from a different namespace.

This bumps the PHP version requirement from 5.6 to 7.1. This allows us to bring in a load of cool new stuff, including a far more up-to-date version of PHPUnit.

It’s available now on GitHub and Packagist.

Photo credit: Brett Donovan.

Heroku for PHP course

Wednesday, March 14th, 2018 | News, Programming

I’m pleased to announce the launch of my new course, Heroku for PHP. I love Heroku as a hosting platform, and there were no Udemy courses on how to use it with PHP. So, I’ve filled the gap. For a very reasonable $29.99.

You can check it out here, and that link will save you an additional $10, too.

How to serve .well-known on Heroku

Saturday, April 22nd, 2017 | Programming

If you want to use Apple Pay on your website, you need to serve a file from inside the .well-known directory. This is a nightmare.

For a start, you can’t create a directory called .well-known on Mac. So you have to find another solution. mod_rewrite to the rescue perhaps? You may be able to get this working, but I couldn’t. I just got a 403 permission denied on Heroku.

client denied by server configuration: /app/public/.well-known

So I decided to try Alias instead.

This was easy to configure on my localhost. I created a directory without the period and then used Alias to map it.

Alias "/.well-known" "/Users/me/Projects/ProjectName/well-known"

To configure it in Heroku, I created a custom Apache configuration file.

Alias "/.well-known" "${HEROKU_APP_DIR}/well-known"

And then configured my Procfile to load this.

web: vendor/bin/heroku-php-apache2 -C heroku-apache.conf public/

So far, so good. Except this didn’t work either because I had not given the directory permission in the Apache config. So I expanded my custom config file.

<Directory "${HEROKU_APP_DIR}/well-known">
    Options FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

This got it working. However, it took my homepage offline. It seems that when you include a custom Apache configuration file, it knocks out the standard DirectoryIndex that Heroku has. Or, there is something else really obviously wrong that I am missing. That’s entirely possible, but I haven’t spotted it yet.

And I did manage to fix it by adding in a new DirectoryIndex to my .htaccess file.

DirectoryIndex index.php

Finally, you have Heroku serving the directory correctly.

How to install Composer inside WordPress

Sunday, March 26th, 2017 | Programming

WordPress has an ancient and ugly codebase. When you are writing plugins, you often want to make your code less horrible and use Composer to bring in dependencies as normal. How do these things fit together?

The first thing I tried was to create plugin-specific dependencies list. Inside the plugin directory, I created a composer.json> file, then ran the install and uploaded it.

This approach works fines when you only use a dependency once and no other plugin uses the same dependency. However, when I wanted to re-use a library in another plugin I had a problem. If I included it twice it would crash.

Installing Composer globally

The solution I came to was to do a global install of Composer. I created a composer.json file in the root and listed all of the dependencies I needed for my plugins in there.

Then, in each plugin, I checked for the file and included it.

if (file_exists(sprintf('%s/vendor/autoload.php', ABSPATH))) {
    require_once sprintf('%s/vendor/autoload.php', ABSPATH);
    add_filter('the_content', 'simple_related_posts', 30);
} else {
    add_action('admin_notices', function() {
        echo '<div class="notice notice-warning is-dismissible"><p><em>Simple Related Posts</em> plugin requires <strong>Composer</strong>.</p></div>';
    });
}

Drawbacks to this approach

This means plugins are no longer self-contained. You have to install the plugin and add the dependencies to your central composer.json file. That is messy.

With the code above, it does not check if the _correct_ dependencies are there. This is something you could add in. However, given the problem above, it seems like it would require so much manual management anyway that I would know what was going on.

Optimise for performance

If you are using Composer on production, do not forget to optimise the autoloader before uploading your files.

composer dumpautoload -o

Conclusion

Personally, I would rather lose plugins being entirely self-contained and benefit from Composer libraries. This approach works well for me because they are purpose-written plugins for my blog.

However, this approach would not work well if you were publishing your plugins as most WordPress users would not know what was going on.

How to run Webpack Dev Server with PHP

Monday, March 20th, 2017 | Programming

You are making an awesome new JavaScript-based app in React. However, it needs to live inside an existing framework, such as Symfony for PHP. You want the hot-reloading function that you get with Webpack Dev Server, but you also need the content to be served from your LAMP stack because it creates the HTML wrapper page.

What do you do?

The answer is to run two servers: both Webpack Dev Server and your existing Stack. Here is how…

Configuring Webpack

Start by installing Webpack Dev Server as usual.

npm install --save-dev webpack-dev-server

Create a script entry in package.json to run it:

"scripts": {
    "start": "node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js"
}

We also need to make one change to our webpack.config.js file. We need to tell the output to point back at the Webpack Dev Server. Otherwise, it will point at your LAMP stack.

output: {
    publicPath: 'http://localhost:8080/scripts/'
}

Super. Next, let’s configure the LAMP stack.

Configuring your existing server

This step is easy. All we need to do is to point the JavaScript at the Webpack Dev Server.

Let’s say you have this at the moment:

<script src="/scripts/app.js"></script>

Change it to:

<script src="http://localhost:8080/scripts/app.js"></script>

Remember that when you are building for production, you will want to take the changes out. The easiest way to do this is to have a dev webpack.config.js file, and a production one, and have your build tool use the correct one depending on the context.