fr8train/skeleton-ms

Skeleton MicroService powered by Slim4 Framework.

2.4.1 2025-04-08 22:32 UTC

README

PHP poweredby Bitbucket open issues

Skeleton MicroService powered by Slim4 Framework.

Installation

Use Composer to install SkeletonMS locally.

composer create-project fr8train/skeleton-ms [directory] [version] 

If you want to include the git link to send updates to the repository, use:

composer create-project --keep-vcs fr8train/skeleton-ms [directory] [version]

Post-Installation

Please create a .env file at the root directory of the project.

Seriously, I just ran into this again. Without at least a blank .env file, you will receive a HTTP 500 error. A blank one is required for the project to run, and we recommend that you use it as the location to store any sensitive information such as passwords or keys. To retrieve any data stored here, use vlucas/phpdotenv.

If for some reason you get an error message indicating that a class in the code cannot be found, first try to reload the autoloaded classes through Composer.

composer dump-autoload

Useful Design Practices and Libraries

ramsey/uuid - PHP UUID libray

This was added to our project as increase for working with UUIDs continues to rise and leave predictable int unsigned IDs behind. Here's a link to his documentation.

sorskod/db PDO wrapper

This is a great little library for making DB work a lot simpler to manage. Information and practices can be found at his Github Page or his similar Packgist. One thing that isn't implicit in his documentation to note, if you would like to bind your parameters for SQL Injection scrubbing please follow this example:

$results = $this->db->execQueryString("insert into your_table (col_a, col_b, col_c)
values (:cola, :colb, :colc)", [
                'cola' => $valueA,
                'colb' => $valueB,
                'colc' => (int) $valueC // EXAMPLE: say col_c was created as a boolean,
                // don't forget to cast to type int or it will throw an error
]);

CastableTrait

The Castable Trait provides a quick assignment to your PHP custom objects or classes as long as you remember to include default values in the constructor of your custom object or class.

// CUSTOM CLASS DEFINITION
class ServiceResponse
{
    use CastableTrait;

    public int $http_code;
    public string $message;
    public mixed $payload;
    public Exception|Throwable|null $error;

    public function __construct(int       $http_code = 200,
                                string    $message = '',
                                mixed     $payload = null,
                                Exception|Throwable $error = null)
    {
        $this->http_code = $http_code;
        $this->message = $message;
        $this->payload = $payload;
        $this->error = $error;
    }
}

// ACTUAL CASTING
// BECAUSE OF DEFAULT VALUES ANY ONE OF THESE PROPERTIES CAN ACTUALLY BE SKIPPED IN DECLARATION
return ServiceResponse::cast([
    'http_code' => 500,
    'message' => 'Internal Server Error',
    'payload' => [
        'trace' => $exception->getTrace()
    ],
    'error' => $exception // instanceof Exception, Throwable, or null
]);

ServiceResponse Service Design

Implementing Services by always returning ServiceResponses can prove to make your code much easier to navigate and reduce bugs by following the design:

<?php

namespace services;

use models\HelloWorld;
use models\ServiceResponse;

class HelloWorldService
{
    public static function hello(HelloWorld $world) :  ServiceResponse
    {
        /*
         *  DO YOUR SERVICE LOGIC
         */

        // ANY OF THESE PROPERTIES ON SERVICE RESPONSE CAN TECHNICALLY BE BLANK BECAUSE OF DEFAULT VALUES
        if (isset($exception)) {
            return ServiceResponse::cast([
                'http_code' => 500,
                'message' => 'Internal Server Error',
                'payload' => [
                    'trace' => $exception->getTrace()
                ],
                'exception' => $exception // instanceof Exception, Throwable, or null
            ]);
        }

        return ServiceResponse::cast([
            'message' => $world->message,
            'payload' => [
                'Foo' => 'Bar'
            ]
        ]);
    }
}

See how handling the ServiceResponse makes Controller handling much easier:

$serviceResponse = HelloWorldService::hello(ObjectFactory::loadClass(HelloWorld::class, $data));

return match ($serviceResponse->http_code) {
    200 => $this->json($response, [
        'message' => $serviceResponse->message,
        'payload' => $serviceResponse->payload,
    ]),
    default => $this->error($response, $this->log, $serviceResponse)
};

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Please make sure to update tests as appropriate.

License

MIT