On Mon, Jun 16, 2025, at 20:10, Larry Garfield wrote:
> On Mon, Jun 16, 2025, at 10:18 AM, Olle Härstedt wrote:
> > Hello Internals,
> >
> > I was pondering a little about effect handlers today, and how they could
> > work as a replacement for dependency injection and mocking. Let me show an
> > example:
> >
> > <?php
> >
> > require_once("vendor/autoload.php");
> >
> > use Latitude\QueryBuilder\Engine\MySqlEngine;
> > use Latitude\QueryBuilder\QueryFactory;
> > use function Latitude\QueryBuilder\field;
> >
> > // Dummy db connection
> > class Db
> > {
> >     public function getQueryBuilder()
> >     {
> >         return new QueryFactory(new MySqlEngine());
> >     }
> > }
> >
> > interface Effect {}
> >
> > class QueryEffect implements Effect
> > {
> >     public $query;
> >
> >     public function __construct($query)
> >     {
> >         $this->query = $query;
> >     }
> > }
> >
> > class Plugin
> > {
> >     /* The "normal" way to do testing, by injecting the db object. Not
> > needed here.
> >     public function __construct(Db $db)
> >     {
> >         $this->db = $db;
> >     }
> >     */
> >
> >     public function populateCreditCardData(&$receipt)
> >     {
> >         foreach ($receipt['items'] as &$item) {
> >             // 2 = credit card
> >             if ($item['payment_type'] == 2) {
> >                 $query = $this->db->getQueryBuilder()
> >                     ->select('card_product_name ')
> >                     ->from('card_transactions')
> >                     ->where(field('id')->eq($item['card_transaction_id']))
> >                     ->compile();
> >
> >                 // Normal way: Call the injected dependency class directly.
> >                 //$result = $this->db->search($query->sql(),
> > $query->params());
> >
> >                 // Generator way, push the side-effect up the stacktrace
> > using generators.
> >                 $result = yield new QueryEffect($query);
> >                 if ($result) {
> >                     $item['card_product_name'] =
> > $result[0]['card_product_name'];
> >                 }
> >             }
> >         }
> >     }
> > }
> >
> > // Dummy receipt
> > $receipt = [
> >     'items' => [
> >         [
> >             'payment_type' => 2
> >         ]
> >     ]
> > ];
> > $p = new Plugin();  // Database is not injected
> > $gen = $p->populateCreditCardData($receipt);
> > foreach ($gen as $effect) {
> >     // Call $db here instead of injecting it.
> >     // But now I have to propagate the $gen logic all over the call stack,
> > with "yield from"? :(
> >     // Effect handlers solve this by forcing an effect up in the stack
> > trace similar to exceptions.
> >
> >     // Dummy db result
> >     $rows = [
> >         [
> >             'card_product_name' => 'KLARNA',
> >         ]
> >     ];
> >     $gen->send($rows);
> > }
> >
> > // Receipt item now has card_product_name populated properly.
> > print_r($receipt);
> >
> > ---
> >
> > OK, so the problem with above code is that, in order for it to work, you
> > have to add "yield from" from the top to the bottom of the call stack,
> > polluting the code-base similar to what happens with "async" in JavaScript.
> > Also see the "Which color is your function" article [1].
> >
> > For this design pattern to work seamlessly, there need to be a way to yield
> > "all the way", so to speak, similar to what an exception does, and how
> > effect handlers work in OCaml [2].
> >
> > The question is, would this be easy, hard, or very hard to add to the
> > current PHP source code? Is it conceptually too different from generators?
> > Would it be easier to add a way to "jump back" from a catched exception
> > (kinda abusing the exception use-case, but that's how effect handlers work,
> > more or less)?
> >
> > Thanks for reading :)
> >
> > Olle
> 
> Algebraic effects is a... big and interesting topic. :-)  If we were to go 
> that route, though, I would want to see something more formal than just a 
> "yield far."  That's basically another kind of unchecked exception, whereas I 
> want us to move more toward checked exceptions.
> 
> --Larry Garfield
> 

I think this might be entirely possible via an extension... there might need to 
be some hooks added to php-src but nothing that would require an RFC.

— Rob

Reply via email to