fr8train / skeleton-ms
Skeleton MicroService powered by Slim4 Framework.
Requires
- php: >=8.0
- ext-json: *
- ext-pdo: *
- guzzlehttp/guzzle: ^7.9
- monolog/monolog: ^3.8
- nesbot/carbon: ^3.8
- php-di/php-di: ^7.0
- ramsey/uuid: ^4.7
- slim/psr7: ^1.7
- slim/slim: ^4.14
- sorskod/db: ^1.1
- vlucas/phpdotenv: ^5.6
README
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.