Data
Working with data within the Slim 4 framework involves four main components:
- Interfaces - These are object interfaces that define the methods that a repository class must implement.
- Repositories - These are classes that implement the interfaces and provide the functionality to interact with the data source.
- Services - These are classes that provide the business logic for the application. They use the repositories to interact with the data source.
- Models - These are classes that represent the data in the application.
The purpose of using interfaces and repositories is to abstract the data source from the rest of the application. This allows for easier testing and changing of the data source in the future.
Interfaces
The interfaces are located in the src/Application/Domain/Interfaces directory.
The methods defined in these interfaces can be up to the developer, but typically
they will include the CRUD operations for the data source.
As a general rule in this skeleton, methods use the standard operation names for:
create(...)update($id, ...)delete($id)
For "read" operations, there is the standard mix of using "find" vs "get". In this
skeleton, "find" is used when the result is, in theory, unknown. For example, when
searching for a user by email address, the method is findByEmail($email).
"Get" is used when the result should be known to exist. For example, when getting
all users, the method is getAll().
Repositories
The repositories are located in the
src/Application/Infrastructure/Persistence/Repositories directory. These classes
implement the interfaces and provide the functionality to interact with the data
source.
The repositories are responsible for the actual interaction with the data source. This can be a database, an API, a file system, or any other data source.
It is important to note that the repositories should not contain any business logic. They should only contain the logic to interact with the data source.
Services
The services are located in the src/Application/Domain/Services directory.
These classes provide the business logic for the application. They use the
repositories to interact with the data source.
The services are responsible for the business logic of the application. This can include things like validation, calculations, and other logic that is specific to the application.
Models
The models are located in the src/Application/Domain/Models directory. These
classes represent the data in the application.
The models are responsible for defining the structure of the data in the application. This can include things like properties, relationships, and other data-related information.
In this skeleton, all models are also required to implement the JsonSerializable
interface. This allows for easy serialization of the models to JSON for returning
to the client and better support for logging tools.
Factories
Factories are used to create instances of classes. In this skeleton, factories are used to create instances of models from arrays of data.
The factories are located in the src/Application/Domain/Factories directory.
In this skeleton, all factories that create models extend the BaseModelFactory
class. This provides a mechanism for checking the provided data for required fields
in order to successfully create the model instance and throws a ValidationException
if any required fields are missing.
End-to-End Examples
The following examples show how the data components work together in the context of a simple user management system.
List all users
The route [GET] /api/users is requested. This then invokes the ListUsersAction
class, which extends the UserAction class.
In the UserAction class, the UserService class is instantiated.
In the action() method of the ListUsersAction class, the getAll() method of
the UserService class is called. This then calls the getAll() method of the
UserRepositoryInterface interface.
In the app/repositories.php file, the UserDatabaseRepository class is bound to the
UserRepositoryInterface interface. This means that when the getAll() method is
called on the UserRepositoryInterface interface, the getAll() method of the
UserDatabaseRepository class is actually called.
The UserDatabaseRepository class then interacts with the data source to get all
users. This data is then returned back to the UserService class.
The UserService class then loops through the data and creates instances of the
User model using the UserFactory class.
The User models are then returned to the ListUsersAction class, which then
returns the data to the client through an Action class that will serialize the
data to JSON.
Create a new user
The route [POST] /api/users is requested with the required data in the request
body. This then invokes the CreateUserAction class, which extends the
UserAction class.
In the UserAction class, the UserService class is instantiated.
In the action() method of the CreateUserAction class, the getFormData()
method of the UserAction class is called to get the form data from the request.
The CreateUserAction class then uses the UserFactory class to create a new
instance of the User model from the form data.
The CreateUserAction class then calls the create() method of the
UserService class, passing in the new User model.
The UserService class then creates a new UserValidator instance and validates
the new User model. If the validation fails, a ValidationException is thrown.
If the validation passes, the create() method of the UserRepositoryInterface
interface is called, passing in the new User model.
In the app/repositories.php file, the UserDatabaseRepository class is bound
to the UserRepositoryInterface interface. This means that when the create()
method is called on the UserRepositoryInterface interface, the create()
method of the UserDatabaseRepository class is actually called.
The UserDatabaseRepository class then interacts with the data source to create
the new user. The new user id is then returned back up the chain to the
CreateUserAction class.
The CreateUserAction class then uses the findById() method of the
UserService class to get the new user by id. This data is then returned to
the client through an Action class that will serialize the data to JSON.
If the new user cannot be found, an Exception is thrown and caught by the
HttpErrorHandler class, which then returns an error response to the client.