Your idea about using a simple condition for this is good, no need to make it more complex.
Algorithm updates after some date
Hello,
I have this problem.
To calculate some amount, I have an algorithm that takes into account some properties.
After March 1., the algorithm will change.
What's the best way to automatically execute algorithm 1 for all calculations before March 1. and algorithm 2 for all calculations after March 1. ?
I thought about writing a simple condition with the date.
Is there other ways to do that ?
Thanks for your suggestions.
V
@vincent15000 Create classes for both algorithms that adhere to a common interface. Conditionally bind the implementation in a service provider based on the date:
$this->app->singleton(function (): Calculator {
return Carbon::create(2024, 3, 1)->isPast()
? new NewAlgorithm()
: new OldAlgorithm();
});
@martinbean I have never written a ServiceProvider, I just had a look around this some months ago.
You mean a common interface, is the Calculator the interface in your example ?
What if the calculation depends on model properties ? Using your example, does it mean that I can implement my model with the Calculator interface ?
What would be nice is that I can use my model to calculate the amount.
I'd like to keep the idea of the service provider, but now the question is how to use it with my model so that it's possible for example to calculate the amount like this.
$session = Session::find(15);
$amount = $session->calculate();
@vincent15000 Yes, Calculator would be the interface, as I didn’t know what you were calculating as you just said “some amount”.
What would be nice is that I can use my model to calculate the amount.
If your class requires a model to perform calculations, pass the model as an argument:
$calculator->calculateFor($session);
@martinbean Such a way I don't need to implement my model with the interface. It's just necessary to use the service provider and pass the session as an argument.
That's very interesting and it will be my first service provider, great ;).
@martinbean The calculation method will change on March 1., but the user has to be able to calculate for example on March 10. an amount for sessions realized on February.
Is a service provider the right choice yet ?
@vincent15000 In that case, it seems a date is a required argument when calculating dates. So have a calculator class that accepts both the date and whatever model, and returns the calculated value for that date and model combination:
$calculator->calculateFor($date, $session);
The calculateFor can then use the $date to determine which algorithm to use to calculate whatever value:
public function calculateFor(DateInterface $date, Session $session): int
{
if (Date::create(2024, 3, 1)->gt($date)) {
// Given date is less than March 1, 2024.
// Calculate value using "old" algorithm...
} else {
// Given date is equal to or greater than March 1, 2024.
// Calculate value using "new" algorithm...
}
}
@martinbean Ok thank you.
Hmmm when I see that, it looks like it could be a simple function that could be inside the model or inside the controller (it's an API for me, so inside the controller, a method that returns the calculated value)
The calculation will depend on the date session (before or after March 1.).
Is a service provider really usefull in that case ? Perhaps yes in order to abtract the business logic inside a service and provide this service through the entire application. But I need this calculation functionality only for the sessions.
So perhaps a simple service class ? Or is there a real benefit to use a service provider ?
@vincent15000 A service provider is for providing services, to Laravel’s service container.
If you want to be able to type-hint dependencies in classes, i.e.
class FooController extends Controller
{
protected Calculator $calculator;
public function __construct(Calculator $calculator)
{
$this->calculator = $calculator;
}
}
Then this is when you’d use a service provider. The service provider would tell Laravel what to do when someone type-hints Calculator. Particularly if Calculator was just an interface:
public function register(): void
{
$this->app->singleton(Calculator::class, function () {
return new SpecificCalculatorImplementation();
});
}
@martinbean That's now clear for me.
What I'm trying to do now is to pass the session as an argument to the service provider so that it can automatically return the right implementation according to the session date.
$this->app->bind(CalculatorInterface::class, function (Application $app) {
if ($session->date ...) {
return new CalculateWithAlgorithm1;
} else {
return new CalculateWithAlgorithm2;
}
});
But I really don't know how to pass an argument and perhaps it's not possible. I have tried to pass the argument beside the application one, but I get an error.
I have tried to understand another way to write the binding.
$this->app
->when(ApiSessionController::class)
->needs(CalculatorInterface::class)
->give(function () {
return new CalculateWithAlgorithm1;
});
But here again, I don't understand how or where I could pass a session as an argument.
What I'm trying to do now is to pass the session as an argument to the service provider
@vincent15000 You don’t. Why are you trying to? Service providers are called every time the application is booted. How is Laravel meant to know which session you want to use? This is why I’ve said to pass the session as an argument, i.e. when you actually call the method on your calculator class.
Again:
$calculator->calculateFor($session);
@martinbean Yes effectively you said me that. And sure the application can't know which session I want to use.
Hmmm ... I think that all is now very clear for me.
I think that I have several possibilities to do what I need with the calculation depending on the date of the session :
- a function inside the model so that I can call it via the model, but the function will be somehow big, so it's probably not the best solution
$session->calculatePrice();
- a simple service that I call from inside the controller
$calculationService->calculateFor($session);
- a service provider to provide the service to all the application, but I'm not sure it's very useful in my case because the calculation only depends on some session's properties, now that you have explained me, I think that a service provider is over all useful if the resolution is independant from the each model caracteristics and would have been useful if I had different calculation methods for example according to different models (session, courses, ...)
Well ... these was my thoughts ;).
Thank you very much for all your explanations ;).
@vincent15000 Creating a dedicated class for calculating means you have separation of concerns. The calculator class does one thing and one thing only: calculates a value.
You could stuff the calculation logic in your Session model but, as you’ve said, it then means your model is growing with lots of additional concerns.
Please or to participate in this conversation.