DEV Community

Cover image for Getting started with Slim PHP framework by building a very simple MVC/OOP app
charliedeveloper
charliedeveloper

Posted on

Getting started with Slim PHP framework by building a very simple MVC/OOP app

Aim:
We are going to build a Slim application that creates a farm pen and outputs the noises that the cow & chicken inside the pen make.

Requirements:
- A pen can only contain a cow and a chicken (will help us practice with slim dependancy injection container)
- Must implement an MVC approach (help us understand how models, views and controllers work together)

1. So we are gonna need the slim skeleton application - open your command line and run:

composer create-project slim/slim-skeleton slimFarm

  • if you don’t have composer installed globally use the composer documentation to get it

2. On your command line navigate (cd) into slimFarm directory that has been made for you

3. We are going to run a php server so we can see that the slim app is present and working

  • In your command line run: php -S 0.0.0.0:8080 -t ./public/

4. Go to http://0.0.0.0:8080/ to check its working (you should get the slim default page)

5. Open files in your text editor and make a Classes folder inside src

6. Add your namespace to autoloading in composer.json:

"autoload": {
  "psr-4": {
    "Farm\\": "src/classes/"
  }
},
Enter fullscreen mode Exit fullscreen mode
  • n.b: this is in addition to the already existing autoload-dev

  • This will mean we can reference our classes using namespaces rather than having loads of require's everywhere.

7. Open a new tab in your command line and run composer dump-autoload in the command line

  • This regenerates our autoloading and namespaces to contain the changes we just made in step 6.

8. We need our Cow and Chickens to go in the pen! so make our classes inside Classes/Models folder:

namespace Farm\Models;

class ChickenModel
{
    public $speak = 'cluck';
}
Enter fullscreen mode Exit fullscreen mode
namespace Farm\Models;

class CowModel
{
    public $speak = 'moo';
}
Enter fullscreen mode Exit fullscreen mode

9. Create a Pen class inside Classes/Models folder:
namespace Farm\Models;

class PenModel
{
    public $cow;
    public $chicken;

    public function __construct($cow, $chicken)
    {
        $this->cow = $cow;
        $this->chicken = $chicken;
    }

    public function getCowNoise(){
        return $this->cow->speak;
    }

    public function getChickenNoise(){
        return $this->chicken->speak;
    }
}
Enter fullscreen mode Exit fullscreen mode

10. Lets make a factory so that Pen can be created quickly with its dependencies in just one call.

  • Make a Factories folder (inside the Classes folder)
  • Make PenModelFactory:
namespace Farm\Factories;

class PenModelFactory
{
    function __invoke()
    {
        $cow = new \Farm\Models\CowModel();
        $chicken = new \Farm\Models\ChickenModel();
        return new \Farm\Models\PenModel($cow, $chicken);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Our factory doesn't need a constructor function, the logic is put inside the __invoke magic method because of the way it is called from our Dependency Injection Container (DIC)

11. So now we have made our factory let's put it in our DIC

  • Think of the DIC as a big associative array that knows how to instantiate objects for you we put in there for use later in our application
  • Open up src/dependancies.php
  • There are preexisting things inside the DIC already
  • Add our factory like this:
$container['penModel'] = new \Farm\Factories\PenModelFactory();
Enter fullscreen mode Exit fullscreen mode
  • We are saying "inside our container (DIC) add a new key of 'penModel' and when we ask for that 'penModel' run our factory to instantiate and return a ready made pen object with all the dependencies (cow + chicken) taken care of."

12. Let’s look in our routing file to see how we deal with different HTTP Requests to our application.

  • Like the defaults already written in here we could use quick anonymous functions to save having to create a factory but we instead are gonna "do it right" so we will make a route and use a controller to call our business logic.
  • Delete the existing $app->get(.....) routes already in the file (we will create our own)
  • Set up our route:
$app->get('/makeMeAPen', <where our callback will go> );
Enter fullscreen mode Exit fullscreen mode
  • /makeMeAPen is the URL extension people are expecting to visit to run our code
  • But we don't have a controller yet so....

13. Let’s make a controller to call our PenFactory and render out a view:

  • Create a Controllers directory inside src/Classes
  • Create PenController inside src/Classes/Controllers/
namespace Farm\Controllers;

class PenController
{
    protected $container;

    //this constructor passes the DIC in so we can get our PenFactory out of it later
    function __construct($container)
    {
        $this->container = $container;
    }

    function __invoke($request, $response, $args)
    {
        //create our pen from Penfactory in DIC
        $pen = $this->container->get('penModel');

        //assign args (variables that will be available on rendered view)
        $args['cowNoise'] = $pen->getCowNoise();
        $args['chickenNoise'] = $pen->getChickenNoise();

        //get the default template renderer out of DIC and pass the response and $args to a template file
        return $this->container->get('renderer')->render($response, 'showFarm.phtml', $args);
    }
}
Enter fullscreen mode Exit fullscreen mode

14. Now our controller exists lets add it in as the thing that happens when our route is hit:

$app->get('/makeMeAPen', \Farm\Controllers\PenController::class);
Enter fullscreen mode Exit fullscreen mode

15. Notice in our PenController we are calling a template file that doesn't exist yet so lets create it:

  • Create showFarm.phtml in /templates folder
  • Use the values you put into the $args associative array as normal variables:
<!DOCTYPE html>
<html>
    <head>
        <title>slimFarm</title>
        <style>
            h2 {
                font-size: 10rem;
                color: red;
            }
        </style>
    </head>
    <body>
            <h2>Cows go: <?php echo htmlspecialchars($cowNoise); ?>!</h2>
            <h2>Chickens go: <?php echo htmlspecialchars($chickenNoise); ?>!</h2>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

16. Navigate to http://0.0.0.0:8080/makeMeAPen and behold the brilliance of your super advanced app!

Oldest comments (8)

Collapse
 
charliedevelops profile image
charliedeveloper

Thanks! Really pleased you liked it!

Collapse
 
bvrulez profile image
bomben • Edited

I like this tutorial very much for getting a basic grasp on what is going on. But there is no src/dependencies.php (also no dependAncies.php). Is this due to a new version of slim? How can we proceed anyway? Thanks a lot!

EDIT: I tried it anyway (creating a depandencies.php and putting in that single line) and got the following error that I do not understand since this file exists: Uncaught Error: Class 'App\Application\Handlers\HttpErrorHandler

Collapse
 
lucasxsl profile image
lucas-xsl

packagist.org/packages/slim/slim-s...

According to the time the article was published, this version has this file;

this commend:
composer create-project slim/slim-skeleton slimFarm 3.1.5

Collapse
 
bvrulez profile image
bomben

I have the error message "Entry \"Farm\\Controllers\\PenController\" cannot be resolved: Parameter $container of __construct() has no value defined or guessable\nFull definition:\nObject (\n class = Farm\\Controllers\\PenController\n lazy = false\n __construct(\n $container = #UNDEFINED#\n )\n)" and think it might be due to this: "In contrast with Slim 2 and Slim 3, Slim 4 does not ship with a DI container, but instead, supports any PSR-11 compatibly DI container that you provide." (akrabat.com/dependency-injection-i...)

But since I am a total noob it could also be another problem. :)

Collapse
 
bvrulez profile image
bomben

The problem was, that the parameter that gets passed to the constructor is tested for its type by slim. So we have to type-hint it now with "Container". But this is just one of the problems this tutorial raises when applied to Slim 4.

Collapse
 
d4rkfrancis profile image
d4rkfr4ncis

Getting the following error:

{
"statusCode": 500,
"error": {
"type": "SERVER_ERROR",
"description": "Call to undefined method Farm\Factories\PenModelFactory::getCowNoise()"
}
}

Collapse
 
zamfir80 profile image
Razvan Zamfir

Great article! You're a great PHP developer aren't you? Will you give me a hand with improving this Slim 3 API? Thanks!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.