Extending PHP classes is a little different from extending other types of entities. You cannot simply replace a class by redefining it in a custom Sprinkle. In fact, classes with the same name in two different Sprinkles would be treated as two different fully-qualified classes per the PSR-4 standard. For example, if I loaded the Sprinkles Account
and Site
, and I had the following structure:
sprinkles
├── account
│ └── src
│ └── Database
│ └── Models
│ └── User.php
├── site
│ └── src
│ └── Database
│ └── Models
│ └── User.php
...then User.php
in site
would not override User.php
in account
. Rather, I'd have two different classes because both classes would have two different namespace : \UserFrosting\Sprinkle\Account\Database\Models\User
and \UserFrosting\Sprinkle\Site\Database\Models\User
.
To actually override and replace the functionality of a class, we have two tools available:
We could, for example, define our User
class in the site
Sprinkle to inherit from the User
class in account
using the extends
keyword:
app/src/Database/Models/User.php :
<?php
namespace \UserFrosting\Sprinkle\MySprinkle\Database\Models;
class User extends \UserFrosting\Sprinkle\Account\Database\Models\User
{
// ...
}
Now, we can start using \UserFrosting\Sprinkle\Site\Database\Models\User
to extend the functionality provided by the User
class in the Account
sprinkle.
Of course, the limitations of object-oriented inheritance becomes clear when you want to change the behavior of the original class in other places where it has been used. For example, if I extended Account\Database\Models\User
and redefined the onLogin
method in my Site\Database\Models\User
class, this would let me use Site\Database\Models\User
going forward in any code I write in the site
Sprinkle. However, it wouldn't affect references to User
in the account
Sprinkle - they would still be referring to the base class.
To allow this sort of "retroactive extendability", the Dependency Injector can be used to resolves interface identifiers to specific class names at runtime through Interface Binding and custom Autowiring.
UserFrosting uses this feature to solve this issue when dealing with data Models by binding each default model to an interface. Rather than hardcoding references to UserFrosting\Sprinkle\Account\Database\Models\User
, UserFrosting reference the UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface
interface and map the interface with the correct model in the Account Sprinkle service provider.
For example, a controller in the account Sprinkle will do something like:
public function __construct(
protected UserInterface $userModel,
) {
}
public function __invoke(Request $request, Response $response): Response
{
// ...
$user = $userModel->where('email', '[email protected]')->first();
// ...
}
...instead of:
public function __invoke(Request $request, Response $response): Response
{
// ...
$user = User::where('email', '[email protected]')->first();
// ...
}
The following interface-model association are defined by default in the Account sprinkle :
Interface | Model |
---|---|
UserFrosting\Sprinkle\Account\Database\Models\Interfaces\ActivityInterface |
UserFrosting\Sprinkle\Account\Database\Models\Activity |
UserFrosting\Sprinkle\Account\Database\Models\Interfaces\GroupInterface |
UserFrosting\Sprinkle\Account\Database\Models\Group |
UserFrosting\Sprinkle\Account\Database\Models\Interfaces\PasswordResetInterface |
UserFrosting\Sprinkle\Account\Database\Models\PasswordReset |
UserFrosting\Sprinkle\Account\Database\Models\Interfaces\PermissionInterface |
UserFrosting\Sprinkle\Account\Database\Models\Permission |
UserFrosting\Sprinkle\Account\Database\Models\Interfaces\PersistenceInterface |
UserFrosting\Sprinkle\Account\Database\Models\Persistence |
UserFrosting\Sprinkle\Account\Database\Models\Interfaces\RoleInterface |
UserFrosting\Sprinkle\Account\Database\Models\Role |
UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface |
UserFrosting\Sprinkle\Account\Database\Models\User |
UserFrosting\Sprinkle\Account\Database\Models\Interfaces\VerificationInterface |
UserFrosting\Sprinkle\Account\Database\Models\Verification |
It is possible for any sprinkle to overwrite the default mapping in a service provider. Then every time UserInterface
is referenced for example, your model will actually be injected instead of the default User
Model.
app/src/ServicesProvider/ModelsServices.php:
namespace UserFrosting\Sprinkle\MySprinkle\ServicesProvider;
use UserFrosting\ServicesProvider\ServicesProviderInterface;
use UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface;
use UserFrosting\Sprinkle\MySprinkle\Database\Models\Members;
/**
* Map models interface to the class.
*
* Note both class are map using class-string, since Models are not instantiated
* by the container in the Eloquent world.
*/
class ModelsService implements ServicesProviderInterface
{
public function register(): array
{
return [
UserInterface::class => \DI\autowire(Members::class),
];
}
}
UserInterface
interface, the Site version will be used.Note that it's not just database models that you can dynamically remap (though they are the most common use case!) Any class references that haven't been hardcoded can be dynamically remapped in another Sprinkle's service. You can learn more about services in Chapter 15.