Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / AWS

Running PHP in Lambda on a Homebrew MVC Framework

5.00/5 (1 vote)
14 Jun 2021CPOL3 min read 4.9K   26  
Build a Custom PHP MVC in 30 mins and deploy to AWS lambda
I decided to write a mini MVC framework in PHP that I thought you may find interesting. You can read about it in this article.

Demo URL:  https://0qdhumcwd5.execute-api.ap-southeast-2.amazonaws.com/Prod/

Introduction

Around a year ago, I put together an article on building your own custom runtime for lambda that enabled you to run PHP. Running PHP this way has a number of benefits, it will scale well, cost very little to host and if you are familiar with PHP and not AWS, then this could be a fun project to try out. Well, over the past year, several people have reached out regarding this and this weekend, I was helping someone get this to work. While remembering how to use PHP, I decided to tinker a little bit and write a mini MVC framework in PHP.

The final application can be seen live here.

Background

For some background, there are a number of good repos available here. Actually a lot of my sample repo is based of this, however if you don't have PHP setup already, this can be hard to deploy and figure out how to install all the dependencies and install composer. So in addition to the code, I added the instructions to setup and run this in Cloud9 as well as a short video.

Using the Code

If you are not using AWS, then SAM might be new to you, I will quickly introduce this. SAM is the Serverless Application Model, it helps you quickly build serverless infrastructure for AWS with a template yaml file. Let's take a quick look at this one:

Please note that I have hard coded the region for the layer I am using:

AWSTemplateFormatVersion: 2010-09-09
Description:  Testing PHP and Lambda
Transform: AWS::Serverless-2016-10-31

##########################################################################
#  Parameters & Globals                                                  #
##########################################################################
Globals:
  Function:
    Timeout: 3
    
Resources:
##########################################################################
#  Lambda function with PHP runtime provided by layers                   #
##########################################################################
  CatchAllLambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      Description: Lambda function to hosts entire application codebase
      CodeUri: ./code/
      Runtime: provided
      Handler: index.php
      MemorySize: 4096
      Timeout: 30
      Tracing: Active
      Layers:
        - 'arn:aws:lambda:ap-southeast-2:209497400698:layer:php-73-fpm:25'
      Events:
        DynamicRequestsRoot:
          Type: Api
          Properties:
            Path: /
            Method: ANY
        DynamicRequestsProxy:
          Type: Api
          Properties:
            Path: /{proxy+}
            Method: ANY
##########################################################################
#  Stack Outputs                                                         #
##########################################################################  
Outputs:
  WebEndpoint:
    Description: "API Gateway endpoint URL for Prod stage"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"

At a high level, this template specified the following:

  • Deploy a lambda function
  • Deploy an API Gateway, so we can invoke the PHP function in a browser.
  • Attach a layer to the lambda function that is prebuilt to support PHP (a layer is additional dependencies required to run your code).
  • Specify the code folder as the source for the code we will deploy, this can contain many files and folders if required.
  • Force the entry point for all incoming requests to index.php.
  • Allocate 4096MB of RAM to this function, and specify a max runtime of 3 seconds.

If we think about this file, it does a lot, it fully replaces building a server, installing PHP, installing Apache of nginx, configuring PHP and server hardening. This is a huge benefit and time saver.

Deployment

Rather then adding static images, I have opted to make a small video:

Create a new Cloud9 Environment, the smallest instance size will be just fine, but ensure you select Amazon Linux 2.

In Cloud 9, perform the following steps in the terminal:

PHP
sudo yum -y update
sudo amazon-linux-extras install -y php7.2 php-mbstring

git clone https://github.com/kukielp/aws-lambda-php-mvc.git

cd aws-lambda-php-mvc

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"

This installs PHP 7.2, clones the repo and installs a composer, next we will install bref and guzzel.

cd code
php ../composer.phar require bref/bref
php ../composer.phar require guzzlehttp/guzzle

Open the template.yaml, update line 27 to align the region you wish to deploy into. You can look here for available layers: https://runtimes.bref.sh/.

Then run:

cd ..
sam deploy --guided

Follow the instructions and ensure you deploy to the region you specified in the template.yaml.

Once that is deployed, you can open the WebEndpoint from the outputs section in your browser and you'll see the app running.

MVC Code

index.php

PHP
// Include breff/guzzel and other dependencies.
require_once 'vendor/autoload.php';
    
$__path = explode("/", $_SERVER['REQUEST_URI']);

if(count($__path) < 3){
    $__controller = 'controller/base.php';
    $__view = 'view/base/base.php';
    $__controllerMethod = 'base';
}else{
    //Delete the first '/'
    $__pos = strpos($_SERVER['REQUEST_URI'], "/");
    if ($__pos !== false) {
        //extract the view (/sample/page will be --> view/sample/page.php)
        $__view = "view/" .substr_replace($_SERVER['REQUEST_URI'], "/", $__pos, 1) .".php";
        //extract the controller at this point it ill be (sample/page)
        $__controller = "controller/" .substr_replace($_SERVER['REQUEST_URI'], "/", $__pos, 1);
    }
    //Convert to an array and get the last item (page)
    $__method_arr = explode ("/", $__controller);
    $__controllerMethod = end($__method_arr);
        
    //Remove the last item in the path (sample/page --> sample)
    array_pop($__method_arr);
    //rebuild the path to the controller (controller/sample.php)
    $__controller = implode ($__method_arr, "/") .'.php';        
}
// Include the controller which provides access to....
include $__controller;
// the methods we will call (in this case page), the result will be placed back in $_rc
$_rc = call_user_func($__controllerMethod);
// include the view, the view should only ever access $_rc no other scopes for data.
include $__view;

All this code does is basically parse out which two files to "include" and which method to run. $__rc is used as the request context variable in which to pass information from the controller to the view. The controller will handle any business logic, com's to the database or external source and the view should contain only basic logic for display purposes.

random.php (sample controller)

PHP
function joke() {

    $client = new GuzzleHttp\Client();
    $res = $client->get('https://official-joke-api.appspot.com/random_joke');
    $json = $res->getBody();
    $result = json_decode($json);
    $_rc = [
        "httpResult" => $result
    ];
    return $_rc;
}

joke.php (sample view)

PHP
<html>
    <body>
        <h1>This is the Random Joke page!.</h1>
        <p>Enjoy the Joke Text:</p>
        <div>Setup: <?php echo $_rc["httpResult"]->setup; ?></div>
        <div>Punchline: <?php echo $_rc["httpResult"]->punchline; ?></div>
        <pre>
            <?php 
                krumo($_rc["httpResult"]);
            ?>
        </pre>
        <a href="/Prod">Home</a>
    </body>
</html>

Image 1

Krumo is included for basic debugging.

Repository: https://github.com/kukielp/aws-lambda-php-mvc

This was a fun little experiment and I hope you enjoyed reading it!

History

  • 14th June, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)