mkuettel / codeigniter4-services
Service Layer Library for CodeIgniter 4
This package's canonical repository appears to be gone and the package has been frozen as a result.
Fund package maintenance!
Buymecoffee
Liberapay
Requires
- php: ^8.3
- codeigniter4/framework: ^4.5
Requires (Dev)
- ext-sqlite3: *
- codeigniter/coding-standard: ^1.7
- codeigniter/phpstan-codeigniter: ^1.4
- fakerphp/faker: ^1.23
- friendsofphp/php-cs-fixer: ^3.54
- mikey179/vfsstream: ^1.6
- mockery/mockery: ^1.6
- phpunit/phpunit: ^10.0
- prewk/result: ^3.3
- squizlabs/php_codesniffer: 3.*
This package is auto-updated.
Last update: 2024-11-29 01:37:44 UTC
README
This library should make it easier to facilitate a Service layer for CodeIgniter4 application. Additionally there are some tools to interact with the model layer, in particular this library contains a Service to have some nicer and more reliable syntax for database transactions.
Installation
You can add this library to your project using composer:
composer require mkuettel/codeigniter4-services
That's it, but you might want to change some of the configuration, as described in the next section.
Configuration
There are multiple configuration files available in the src/Config directory. You can copy these into your project and extend from the classes to override these default configurations.
(a publisher script will be added to copy these files to your project in the future)
Service Interface
This Library provides the interface \MKU\Services\ServiceInterface
.
The idea is that you extend from this interface by defining another interface for your own service. In this interface you define all the methods your service will have:
use \MKU\Services\ServiceInterface;
interface MyBusinessService extends ServiceInterface {
public function placeOrder(Order $order): Result;
public function sendOrderConfirmationMail(Order $order): Result;
// ...
}
You then implement this interface with your own service class.
class SimpleBusinessService extends MyBusinessService {
private OrderModel $orderModel;
private MailService $mailService;
public function __construct(OrderModel $orderModel, MailService $mail) { /* ... */ }
public function shortname(): string { return 'simple_business'; }
public function placeOrder(Order $order): Result { /* */ }
public function sendOrderConfirmationMail(Order $order): Result { /* */ }
// ...
}
You'll also need to implement shortname(): string
method returning a unique identifier for
this service implementation.
The shortname will later be used to get or create an instance the implementing service class using:
service('<shortname>');
\Config\Services::shortname();
But for this to work you must for now register it as follows
class Services extends BaseService {
// ....
// use the same parameters as required by the constructor of the service class,
// but make them null by default.
public function business_simple() (
OrderModel $orderModel = null,
MailService $mail = null,
bool $getShared = true // add extra parameter to reuse past instance if available (e.g. a singleton instance)
): SimpleBusinessService {
if ($getShared) return self::getSharedInstance('simple_business', $config, $db);
return new SimpleBusinessService(
$orderModel ?? model(OrderModel::class);
$mail ?? self::mail_service(),
);
}
// use the interface as return type here
public function business(): MyBusinessService {
// change which service class to use for the MyBusinessService interface here
return self::business_simple();
}
// ....
}
TODO(so this must not be done manually): add ServiceContainer as a base class for \Config\Services which autoconfigures the services depending on their constructor types or annotations and creates the builder methods in \Config\Services.
Transaction Service
This library provides a TransactionService, which allows you to execute a custom function during a database transaction:
// PHP 7.4+ using arrow functions
service('transaction')->transact(fn() => do_database_operation())
// Using anonymous function (or any Closure)
service('transaction')->transact(function() use ($db) {
$db->insert( /* update */ ); ...
$db->update( /* ... */); ...
});
Used in this manner, the transaction service begins a transaction before executing the given closure. If an exception occurs during the transaction, the transaction will be rolled back.
Nested Transactions
Transactions can be nested if the underlying database driver and database support them. Currently these are not fully implemented for all drivers, but this Repository provides a a replacement which extends the CI4 database drivers for SQLite & MySQLi with support for nested transactions by using savepoints.
You can use them by setting the DBDriver
option in your .env file or Database configuration to:
# .env
# ...
# use mysqli driver with support for nested transactions
database.default.DBDriver = '\MKU\Services\Database\Transactional\MySQLi'
# ...
# use SQLite3 driver with support for nested transactions
database.tests.DBDriver = '\MKU\Services\Database\Transactional\SQLite3'
Additionally, different drivers and databases may have different transaction guarantees and isolation levels. Also watch out for autocommit behaviour, which may be enabled by default in some database systems.
DataProvider
s and PersistenceService
s
This library also provides a DataProvider
interface and a PersistenceService
interface.
These interfaces are meant to be used in the service layer to interact with the model layer.
The idea is to separate the data access layer (models, CodeIgniter\Database stuff) from the business logic layer (Controllers),
by only returning entity objects and no database connections handles, query result objects or other database specific objects.
In order for the persistence services to identify and address entities correctly entity classes used as parameters for
data provider or persistence services must implement the ServiceEntity
interface, forcing you to define a
primary key (or to declare that the entity has none). If you have a entity class which extends form the CodeIgniter Entity (which is no requirement)
class you can use the CodeIgniterServiceEntityTrait
to use the defined $primaryKey
property.
Data providers are services which query and provide data as entity objects, but itself do not modify the data.
The following three basic methods are provided by the DataProvider
interface:
interface DataProvider {
public function get($id): ?ServiceEntity;
public function refresh(ServiceEntity $id): ?bool;
public function exists($id): bool;
}
$id
is the primary key of the entity to be queried, this can also be an array of multiple attributes.
You can define this by implementing the ServiceEntity in your entity class.
Persistence services are services are an extension of data providers and also need to implement methods to modify the data they provide. The following additional methods are provided:
interface PersistenceService extends DataProvider {
public function save(ServiceEntity $entity): Result;
public function delete($id): ServiceEntity|bool;
}
Configurable Services
Currently theres just a the MKU\Services\Library\Config\Configurable
interface and the ConfigurableTrait
in
the same namespace, simplifying the implementation of the former interface.
The idea is to that you use the Trait and implement the applyConfig() method in your service to configure it using your own custom codeigniter Config class.
This feature is not yet clearly thought out completely and is very experimental and may be subject to change.
Contributing
Send me a message if you want to contribute to this project. I'm happy to receive feedback and suggestions.
Pull requests and bug reports are welcome at my GitHub repository: