ReactPHP + Event Loops w/ Len Woodward

00:06.60
Chris Morrell
All right, welcome back to Over-Engineered, the podcast where we ask the question, what's the absolute best way to do things we already have a perfectly acceptable solution for? Today, I am here with Len Woodward, and we're gonna be talking a little bit about ReactPHP, which is ah one of those interesting bits of technology that I have played with and always think, ah, There's gotta be a great way to use this, ah but I've yet to find it.

00:37.76
Chris Morrell
it's ah It's my hammer and I'm looking for a nail. um And we were chatting about this a little bit.

00:41.99
Len Woodward
I was literally about to say that same thing.

00:45.46
Chris Morrell
um But before we get into ReactPHP, Len, do you wanna just quickly introduce yourself and say hi?

00:53.39
Len Woodward
Yeah, my name's Len. I've gone online by the handle Project Gopher for like the last 15 or 20 years. um That was like a one man freelance dev agency, but recently I partnered up with my buddy Ed Grosvenor and we're trying to set up our own thing called Artisan Build. And so we're trying to just build some cool shit, have some fun, and ReactPHP was one of those things that I kind of landed on by accident because I was trying to solve some weird problems in Laravel prompts for um a proof of concept we were working on. And I just learned pretty quick how deep that rabbit hole goes.

01:29.35
Chris Morrell
Yeah. Yeah. i um i I think I've talked about this on the show a little bit, but I i have a sort of experimental alternative to Laravel Dusk that I call Dawn that ah is built on top of ReactPHP. And that's my that's my one experience with it. um But I feel like in that case, Excuse me. I feel like I was starting to get a feel for what it could do. But I don't feel like I unlocked the sort of true power of it.

02:03.79
Chris Morrell
And it just kind of left me wanting more. um What was what was it that first got you? What was this? Or can you talk about this sort of problem that you're trying to solve, ah that this became the solution for?

02:14.05
Len Woodward
Oh yeah, no none of this is like industry secrets or whatever. it's It wasn't even client work. It was, um well, I guess technically client work. So the way Ed and I are trying to structure our thing is we're not doing client work with our agency. We're doing technical co-founders as a service. So we bring on someone who's knowledgeable in a field or has a following in that field.

02:29.68
Chris Morrell
Okay.

02:33.78
Len Woodward
They'll partner with us for like the marketing, the mailing list and all of that. And then Ed and I get to solve the hard problems. And so for this specific product, we we didn't want to build the the UI first and get all distracted with that.

02:39.92
Chris Morrell
Interesting.

02:46.34
Chris Morrell
Mm

02:48.70
Len Woodward
We wanted to build the product, solve the problem, and then let the UI happen because we didn't want to be bound to a design.

02:54.42
Chris Morrell
-hm.

02:56.61
Len Woodward
So we're building everything in the command line ah as commands. And the limitation that I ran into was that we reading for waiting for terminal input is a blocking operation, the way it's implemented in prompts. So when you're looking at like terminal.read or terminal arrow read, um it just sits there waiting for the next thing to enter the buffer and then it'll execute its render loop and it'll go through that that whole process. So what I needed to do was I needed to extract that looping mechanism so that we could do other stuff while we're waiting for a keyboard input.

03:37.59
Len Woodward
And so I spoke about this with um with Joe Tenenbaum a little bit.

03:37.63
Chris Morrell
Interesting.

03:43.20
Len Woodward
And he mentioned he he's tried to solve this issue a couple of times, but he hadn't really landed on a solution that he properly liked.

03:50.46
Chris Morrell
Mm-hmm

03:50.59
Len Woodward
And so I looked around at some of the stuff that he did, and his last approach, I don't know if he, I haven't spoken to him about this since I i did this, so I don't know if he knows this, but his final approach was approaching like his own implementation of ReactPHP, because he took a tick-based event approach.

04:12.96
Chris Morrell
Aha

04:13.81
Len Woodward
So I don't know if he realizes how close he was to cracking this, but um yeah, I was able to just extract that looping mechanism, make a few changes. um And that means that we can do, like, we can trigger renders anywhere in the lifecycle. So we can use closure-based callbacks. And inside that closure, then we can call the render method.

04:36.43
Chris Morrell
Okay. And is that happening in, so that's just, that's just happening on an event loop. That's not using like the async await, uh, fiber stuff. Right.

04:48.04
Len Woodward
Right. I mean, I think I'm pretty sure that it uses fibers under the hood and then with those fibers, it uses like um a scheduling mechanism.

04:48.99
Chris Morrell
Okay.

04:58.05
Len Woodward
So it kind of, it uses those features, but it abstracts it all the way.

04:58.15
Chris Morrell
h

05:01.09
Len Woodward
So we don't have to worry about it, which, which is great because fibers breaks my brain.

05:02.90
Chris Morrell
Right.

05:07.33
Chris Morrell
Yeah, it's it's one of those things where at first it felt relatively straightforward when I was looking at the Fibers API, like, okay, I think I can wrap my head around this. But then the moment I started to actually think about all of the sort of implementation consequences, I just walked away.

05:33.19
Len Woodward
Yeah, that's kind of how I fell into it, too, because I was watching a PHP UK talk on YouTube about fibers. And they're talking about concurrency and all of that. And I think I didn't realize in my brain the difference between concurrency and parallelism. so So we're going through that.

05:49.08
Chris Morrell
Hmm.

05:50.87
Len Woodward
I'm like, holy shit, fibers will let me actually run things at the same time. I was looking at that. But it's more like concurrency is just you do this block, and then you do this block, then you do the next one. It's not two blocks at the same time.

06:02.24
Chris Morrell
Mm-hmm.

06:03.57
Len Woodward
So then you dig in a little bit deeper and it says, yeah, like you can use fibers to switch between these different tasks, ah but you still need to schedule them and they need to be done in a non-blocking asynchronous way. So I was trying to look at how to do that and then found another PHP UK talk by, I think his name is Christian Luck. And that was like the proper final light bulb moment for me with ReactPHP.

06:22.71
Chris Morrell
Okay.

06:28.05
Len Woodward
It's like, amazing the type of stuff that this allows you to do. And then they give you all these different packages with different implementations of like the browser or the the terminal or or anything where it's read and written in a non-blocking way.

06:45.76
Chris Morrell
Right. Have you looked into it all? how to implement sort of a library level code that is sort of React PHP compatible, that's async compatible, because that's something that I don't really understand at all. You know, I i understand the principles that, you know, for IO, for example, there's a lot of time just waiting for the file system. So you might as well just ah relinquish control back and

07:20.57
Len Woodward
We've got leaf blowers outside, so I'm going to switch to my headphones.

07:21.03
Chris Morrell
And yeah, no problem. um

07:25.39
Len Woodward
um Can you ask like the last half of that question again so I know when it switched over?

07:28.81
Chris Morrell
Yeah, yeah, yeah. Have you looked into implementing any of the, and sort of any user land library code that is not necessarily explicitly for ReactPHP, but it's just sort of ReactPHP compatible? Because that's something that I'm not quite sure I have my head wrapped around.

07:50.86
Len Woodward
Yeah, so I haven't done it yet. But there's one thing that we're having to do that I think is going to be ReactPHP compatible. I just don't quote me on it because I haven't tested it. So um one of the products we're working on is in the AI space where we're just like assigning different instances of chat GPT, certain personas with different traits and everything. ah But we want to be able to easily switch between providers, so it's not always going to be OpenAI. It's not always going to be in the llama family or whatever.

08:18.17
Chris Morrell
Sure.

08:20.22
Len Woodward
So we're using a service called OpenRouter, and that allows us to route these requests to all sorts of different services. um It supports streaming responses the same way that OpenAI does. But the the OpenAI package that we've got, um the one that that Nuno and I think. Oh shit, who is it that wrote it with them? Was it Ruslan?

08:45.91
Chris Morrell
I'm not sure. I've never used it.

08:47.17
Len Woodward
like Well, um so the the issue with that is that when you're using a streamed response, um when the response comes in, it still waits for the entire buffer to fill, and then it acts after that buffer is filled.

08:59.75
Chris Morrell
Mm hmm.

09:01.36
Len Woodward
So even if you're getting a streamed response, you can't act on it until after the whole thing has come back.

09:06.74
Chris Morrell
Right.

09:06.91
Len Woodward
So ah using the React PHP browser, I was able to interact with the stream response um just natively using their browser, no open AI package. So we just passed our token, our headers, all all the stuff. And then as each chunk comes in, then I operate on those chunks. And in the terminal, what I was able to do because I'm not blocking the the output, I was able to trigger a render with the new response as I'm doing that. So the interesting thing is that if this works, I'm pretty sure the OpenAI package lets you inject your own browser instance, which is like a guzzle compatible or PSR type browser implementation.

09:49.86
Len Woodward
So if I can um inject this ReactPHP browser in there, and then I've got the callbacks that'll allow me to act on the chunks instead of waiting for the end,

09:58.55
Chris Morrell
Oh,

09:59.86
Len Woodward
I think this is going to work. So that would be a really interesting implementation of user land code using this type of thing kind of like from the outside in.

10:02.02
Chris Morrell
oh that's interesting.

10:09.33
Len Woodward
So I don't know if it's going to work, but if it does, I'm going to be super excited because that means I don't have to implement the whole open AI API spec again.

10:09.37
Chris Morrell
Mm hmm.

10:17.94
Chris Morrell
Right, and are you using, I know that there's a couple of implementations of sort of like stream compatible JSON parsing where it does hold a buffer until it gets to sort of a token that it can pause at.

10:29.60
Len Woodward
a

10:36.83
Chris Morrell
um Are you looking at anything like that where you're um you know you're sort of like, partial buffering the responses so that you get some sort of parsable data back or are you just sort of passing that data back on to wherever it's, wherever the original request came from.

10:56.53
Len Woodward
Yeah, I'm pretty much just concatenating it onto onto the variable and then re-rendering the whole variable. So I'm not really having to process anything, because we're just getting text input.

11:02.60
Chris Morrell
Okay.

11:05.54
Len Woodward
We're not getting we're not getting code back, so I'm not having to parse the JSON, and the events that come back are all encapsulated in a little JSON object.

11:16.27
Chris Morrell
Mmhmm.

11:16.34
Len Woodward
So we just, so we parse the event that comes back and in the event we've got a data attribute and in the data attribute is just that one little token.

11:22.87
Chris Morrell
Mmhmm.

11:24.63
Len Woodward
And so I'm taking that token, putting it onto the response and then rendering.

11:29.48
Chris Morrell
Yeah, that's very cool. um It definitely, you know, I've seen that Guzzle has async stuff, um has had async ah calls for a while. um And I, you know, I know I've had to use it in just a few, um few very specific scenarios, but It was never something that I really got my head wrapped around other than understanding enough of the API to make, ah you know, if I needed to make sort of 10 calls at the same time and and didn't want to be ah blocking each one.

12:05.48
Chris Morrell
But, um you know, I know on the HTTP side and on the IO side, there are huge opportunities to write this async-style code because, ah yeah, a lot of the time,

12:05.67
Len Woodward
Mhm.

12:17.87
Chris Morrell
most of what's happening is just waiting, right?

12:18.87
Len Woodward
Mhm.

12:20.95
Chris Morrell
um Waiting for a response, waiting for the the data to be received by the other server, whatever it is. um So that's a huge opportunity to speed things up.

12:33.76
Len Woodward
Yeah, I'd love to dig deeper into this and like understand how these async things actually work, like how promises work as a ah mechanism under the hood, because I understand how to use them and everything, but I feel like there's a gap in my knowledge there where I would love to know I would love to lift the curtain and just see what it is that's happening. Because whenever you get to that point and you see what it's doing, it's almost always so much simpler than you expect it to be. You expect it to be this like magical black box.

13:01.56
Chris Morrell
Yes.

13:04.93
Len Woodward
And then you look at it and you're like, oh my God, yeah, this makes sense.

13:07.37
Chris Morrell
Oh.

13:08.47
Len Woodward
Of course that's how it would work. So I'm eager to get that moment, but I haven't had the opportunity yet.

13:15.63
Chris Morrell
Yeah, especially if the abstractions have been, are like mature and sort of well thought out because they've found those boundaries um in ways that make each discrete piece sort of understandable in itself. and itself um So I thought it would be interesting to talk a little bit about Dawn, this um this browser testing ah experiment.

13:41.45
Len Woodward
Yeah, I've been excited to talk about that because I haven't gotten a chance to go in and properly look at it. So I'm i'm interested to hear about this approach and how it went and what troubles you had.

--- Recording stops here and is reset ---

00:00:28.07
Chris Morrell
Yeah, so part of the problem, or ah well, let me describe what Dawn does, and then we'll maybe we'll work backwards from there. um So essentially, what Dawn does right now is it spawns a couple of background processes that all handle different ah synchronous parts of that ah testing story. right So we've got one background process that manages the Chrome driver. That's the actual tool that's talking to Chrome and telling it to click this button and wait for this JavaScript or whatever whatever you're doing in your actual test.

00:01:12.51
Chris Morrell
um Unfortunately, Chrome driver or the web driver implementation in PHP is written synchronously. So once you tell the browser to do something, ah you're locked until you get the response back from the browser. um So I spawn one background process to manage that. I spawn one background process to run a web server. um And then we've got our sort of quote unquote foreground process, which is where your test is running.

00:01:43.85
Chris Morrell
And so ah in a typical test, you might do a post request to some endpoint, right? So ah if you were doing this in dusk, you would tell it to post to an endpoint that would tell the web driver to make that request to a separate ah local web server that's running your application and get the response back. What we're doing instead is overriding the URL generator so that it generates um yeah URLs that point to our local test server instead. um And that's running a ReactPHP loop.

00:02:28.03
Chris Morrell
that ah listens for incoming HTTP requests and then essentially just serializes that request um with a unique ID um and generates a unique ID and passes those two things along back to the to the parent process over standard out. and then it waits and listens on standard in for, ah well, no, I guess in the web servers, in the in the case of the web server, it doesn't have to be bidirectional. So it's just essentially forwarding those requests back to the main process.

00:03:06.08
Chris Morrell
um

00:03:07.27
Len Woodward
So you were saying that the the actual interaction with the web driver is blocking where where you send.

00:03:07.72
Chris Morrell
and the

00:03:12.94
Chris Morrell
It's blocking as well, yeah.

00:03:14.79
Len Woodward
OK, so did you re implement the web driver so that that one is non blocking?

00:03:20.33
Chris Morrell
No. So what I ended up doing was just spawning a background process for that as well. And so when you make a request, um, when you, when you tell the browser, say, click this button, it's actually serializing that statement and sending it to a background process to execute. Um, and then waiting for the response.

00:03:41.20
Len Woodward
And so that other background process. Yeah. So that other background process is the actual like web requests. So are you just then like queuing a bunch of stuff that's getting sent and then it's, so you're able to do these things a little bit faster.

00:03:58.44
Chris Morrell
Yeah, so there's a there's an event loop in the main thread that's tracking all of this IO between the different processes, right?

00:04:07.40
Len Woodward
Hmm.

00:04:07.49
Chris Morrell
So it's ah it's got, you know, standard in, standard out listeners on the messages sent to the web driver process and the messages sent back from the web driver process. And it's also got,

00:04:20.67
Len Woodward
Mm

00:04:22.18
Chris Morrell
an IO channel to the web server process.

00:04:26.31
Len Woodward
-hmm.

00:04:26.79
Chris Morrell
And so if I make a get request to the home page, right, that's gonna send a serialized payload to the web driver telling it to go to the home page. That request is then gonna hit my background web server process, which is gonna serialize the request and pass it back to the main thread. right, the for foreground process. That foreground process is then going to deserialize that request and essentially just pass it to the HTTP kernel as as though you had made that request in line in your test.

00:04:51.48
Len Woodward
Mmhmm.

00:05:04.09
Chris Morrell
It's going to wait for the response and send the a serialized response back to the web server uh, which will then get sent back to the browser eventually, which will then get sent back to the Chrome driver, uh, process eventually. So it's kind of like everything's going through these multiple hops to get anywhere. But because everything is running in these separate processes, even though there's a lot of inefficiencies, uh, coming from those multiple hops.

00:05:40.24
Chris Morrell
You gain a lot of efficiency by, you know, you're most of the time you're just spending waiting on things to either send or receive. um And all the actual work is happening in these background processes.

00:05:53.56
Len Woodward
So the fact that the HTTP request is blocking isn't actually a huge issue. You're just you're putting a layer in between the the headless a browser and the server so that everything is being done through a queue so nothing interferes with each other and you could do like database operations if you needed to. Uh, but then all of your interactions with the browsers, like each browser headless browser is its own background process then as well. And you're sending, you're giving all of your actions to that process through standard in standard out.

00:06:30.13
Chris Morrell
There's just one single um WebDriver manager that manages all the browsers. So if you open multiple browser windows, it's just one process that kind of orchestrates all of that.

00:06:37.06
Len Woodward
Okay.

00:06:41.97
Len Woodward
Okay, but you're still able to then have multiple browser processes that then don't interfere with each other on the the request level. So you're sure that everything is not polluting each other the other test. Am I understanding that right?

00:06:56.36
Chris Morrell
Sadly, no, it's that is just sort of, that's kind of a fundamental problem with browser testing, is that um you know if you have multiple windows open, the cookies are gonna be persisted across all those windows or tabs or whatever, right?

00:06:56.48
Len Woodward
Or am I still?

00:07:05.03
Len Woodward
Hmm.

00:07:18.27
Chris Morrell
So if I have two browser tests running at the same time,

00:07:19.02
Len Woodward
Oh interesting.

00:07:23.52
Chris Morrell
um there they are going to pollute each other. And that was actually one of my first PRs to dusk was essentially um changing it so that it cleared all the cookies between each test. Because if you didn't know that, and you didn't understand that you had to clear your cookies, you could have um session data persist across different tasks, because the browser was happily holding on to that data, you know?

00:07:59.04
Len Woodward
So the browser instances will each have separate cookies, but the but each browser instance that we reuse between tests, those cookies persist.

00:08:12.22
Chris Morrell
The browser instances. Well, yeah, moz if you're if you're running

00:08:15.96
Len Woodward
Like if you've got browser one, browser two within the same test, those two have separate cookies, right? So that we can have like one user logged in here.

00:08:21.64
Chris Morrell
No, they, they have the same, they have the same cookies. Yeah.

00:08:25.74
Len Woodward
Oh, so how do we have separate users logged in to test that sort of interaction?

00:08:30.91
Chris Morrell
Hmm. That's a great question. ah Maybe there is a way, maybe there is a way to, um, you know, open multiple like profiles inside of chromium or inside of whatever browser you're using. Cause that's a good point.

00:08:46.06
Len Woodward
Hmm.

00:08:47.27
Chris Morrell
I, none of the tests. that I have run have needed to solve that problem. So i haven't I haven't had to worry about like the parallel side on the browser, parallel execution on the browser side. I've only had to worry about it on the test side, but that's a very interesting point.

00:09:05.79
Len Woodward
Have you taken a look through the test suite for Reverb? Because um i feel like I feel like he did this in that test suite where where he had to have two separate browsers interacting.

00:09:12.75
Chris Morrell
I haven't yet, no.

00:09:22.16
Len Woodward
So he would spin up the the Reverb server. He would spin up the two browser instances, logged in as different users. He would take an action and then check the other browser to make sure that he could see that text on there. I feel like I remember seeing that.

00:09:35.85
Chris Morrell
Okay, there must be some way then to do that at the Chrome driver level to spawn a separate browser that that is sandboxed in some way.

00:09:45.59
Len Woodward
he Yeah, I can't imagine that not being there.

00:09:47.03
Chris Morrell
Which is interesting because that opens up the option to do parallel browser testing, which um which I hadn't taken on. But yeah, I mean, theoretically you could have, you know, eight browser windows open or running, uh, in parallel different tasks. Uh, if you can sandbox each browser, I know. And if your machine doesn't start to burn, that's right.

00:10:16.26
Len Woodward
Yeah, well, we're all on roll on M chips now. We're all good. ah Yeah, the the only difficulty then that I could see is.

00:10:21.01
Chris Morrell
That's right.

00:10:25.66
Len Woodward
um is like the database state between these things. Because how does parallel testing work when you're not in a browser? Like, because I know when we're running the test suite, we start a transaction so that we can easily roll it back. But if we're doing parallel testing, oh, we duplicate the database for each of those. Now I remember. Is that something that we could possibly do within dusk? So each each server instance then would have its own database so it can do its own transaction. So maybe we've got the one server, but each thread changes the environment variables so that we're accessing a different testing database.

00:11:06.85
Chris Morrell
Well, if you're using database transactions, then it's okay. I mean, you're basically like. um

00:11:18.53
Len Woodward
Can't you only have one transaction open at a time on a database?

00:11:22.67
Chris Morrell
No, they should.

00:11:25.87
Chris Morrell
I don't think so. I mean, it depends. I think that there's some locking concerns depending on what you do, but, uh, each one Each test is executing in its own database transaction. So they're they're already like isolated from each other. Yeah, this is this is an interesting spot where I have no idea. I have no idea what would happen here. And honestly, it never even occurred to me to consider doing parallel testing with a with browser testing.

00:11:59.51
Chris Morrell
so My whole brain is just like sort of exploding with what what are the implications of this? It's really interesting.

00:12:06.68
Len Woodward
I know we're not going to get any work done for the next two days. We're just going to be hacking on this now.

00:12:12.69
Chris Morrell
Well, so going back to sort of the original um the original conversation, what's interesting is I was looking and someone has re-implemented the web driver package in ReactPHP.

00:12:27.56
Len Woodward
oh

00:12:27.63
Chris Morrell
ah So they've separated out. and i And I actually was thinking about trying this myself because I looked, there's really only one single component in the web driver package that introduces this blocking issue.

00:12:31.89
Len Woodward
Oh.

00:12:43.80
Chris Morrell
And it's like the, um ah you know, it's like the HTTP client, you know, it's like some it's like some custom client that they use to make the request to the Chrome driver instance.

00:12:47.16
Len Woodward
It's standard IO, right? Oh.

00:12:57.32
Chris Morrell
um because that's all over HTTP. It's just like a, or over TCP.

00:13:00.89
Len Woodward
Yeah.

00:13:02.33
Chris Morrell
I don't know if it's HTTP, but it's a client, you know, a network client. um And there's nothing stopping you from splitting the request and the response, um other than that that's the choice that they made, you know ah because typically PHP code is blocking, so there's there's no reason to really think about it async most of the time. um So it seems it seemed relatively doable, and it looks like someone's done that. So now that we have a a async,

00:13:38.95
Chris Morrell
um instance of ChromeDriver, or of WebDriver, which is the um you know the package that talks so to ChromeDriver or to Selenium or whatever. um We already have a async HTTP server, that's what ReactPHP does. right um So it feels to me like we could potentially do everything that Dawn is doing. but all inside of one single ReactPHP event loop without having to spawn any background processes, without having to do any of this crazy standard in, standard out serialization.

00:14:20.50
Len Woodward
Mm hmm.

00:14:21.84
Chris Morrell
um you know i don't I don't fully understand like what the boundaries of like memory access and you know like passing variables around across different ah fibers in PHP.

00:14:43.48
Len Woodward
if

00:14:44.24
Chris Morrell
So I don't know if they're there's some boundary there that I'm not thinking of that would make this harder, it seems to me like like it's doable. and it And it occurred to me, maybe we should like step back. I assume that most people who are listening have at least heard of ReactPHP and maybe understand the premise of an event loop, but maybe we should like just do a quick refresher ah just in case. um

00:15:13.73
Len Woodward
Yeah, for the other five listeners.

00:15:14.22
Chris Morrell
it Yes. um So, you know, the principle behind ReactPHP is this idea of an event loop, which is effectively just a while true with a bunch of things inside of it. um

00:15:32.94
Len Woodward
Yeah, pretty much the same as JavaScript.

00:15:33.46
Chris Morrell
it's Yeah, yep exactly. Yeah, and it's you know it's obviously a lot more complicated than that in terms of like there's ah scheduling and and you know a lot more around manipulat are ah interrupting and intercepting the event loop and stuff like that. But it's essentially just a big while loop. And inside your while loop, you have different things that execute. um And they have ways of sort of pausing their execution either after a certain amount of time or when a certain thing starts to happen um and then resuming their execution at a subsequent cycle of the loop. Does that feel like a good description of the premise?

00:16:23.91
Len Woodward
Yeah, so as far as I understand, um if you give the event loop a blocking operation to do, it's going to kind of mess things up. Like it's going to delay all the the stuff. So in in our event loop, we've got a queue of things that have to happen. And every time we give it a closure with an action and a listener, it's going to queue those up. If you give it a blocking operation, That's going to take as much time as it has to do in that blocking operation. That's why you're not supposed to pass a closure with a sleep call in there.

00:16:54.72
Chris Morrell
right

00:16:54.89
Len Woodward
um And then, yeah, it'll it'll go through. It'll execute things in time. It's got like time trackers. So if you've got stuff that are supposed to happen on a periodic timer, it'll it'll do all those things in order. And because it's in a queue, every time you tell it to do something on the next tick or whatever, you can queue up a whole bunch of tasks there, which is actually how I fake key presses in the testing story for my async prompts.

00:17:16.90
Chris Morrell
Right.

00:17:23.15
Len Woodward
So I just say on next tick, so I take in the array of keys that we're trying to fake, push that onto the event loop under on the next tick, which can guarantee order.

00:17:23.45
Chris Morrell
Okay.

00:17:32.95
Len Woodward
A lot of these other functions can't.

00:17:35.42
Chris Morrell
Mm hmm.

00:17:35.94
Len Woodward
um And then yeah, it just executes them them all. So yeah, what you said is pretty much true.

00:17:39.88
Chris Morrell
Interesting.

00:17:42.85
Len Woodward
It's just a really fancy while true thing, but it's done in a way where you're not losing events. and Things aren't gonna just like time out and get ignored.

00:17:54.37
Chris Morrell
Right, right.

00:17:55.68
Len Woodward
No.

00:17:56.90
Chris Morrell
So like with that in mind, basically, You know, the, the premise of what I'm kind of describing is, um, what we want to be able to do is in our PHP unit test, say, you know, this get slash, you know, and have the results of that execution be.

00:18:28.90
Chris Morrell
uh, some response object that's useful to us for testing. Right. And we want that to have triggered a browser. to make a request to our application and run whatever code is going to happen when you get the homepage. um And that our response is going to be the response of that execution. um And the reason we can't have that in dusk is because of this sort of those are two

00:18:56.65
Len Woodward
Mm hmm.

00:19:00.78
Chris Morrell
two separate processes that need to happen sort of at the same time a problem, but they don't actually have to happen at the same time. They just both have to have a turn, uh, you know, during the same period of time. So kind of what I'm proposing is you wrap the whole test in something that, that is inside the event loop.

00:19:29.73
Chris Morrell
um Or that that doesn't matter so much. there' There are ways to sort of abstract that away. um But essentially, when you do this arrow get, or let's say this browser get, right?

00:19:46.31
Len Woodward
Mm hmm.

00:19:46.80
Chris Morrell
um That is going to create an HTTP request, or it's gonna create a command that gets sent to this WebDriver package that then sends a signal to your browser to go to the homepage, right? um When the WebDriver instance sends that signal to ChromeDriver, for example,

00:20:18.58
Chris Morrell
it's then going to yield control of the loop, right? um And we'll also have a web server ah running in the loop that will be ready to receive the request to slash, to localhost slash, for example, right? um And when it receives that request, it's going to probably put it in a queue somewhere, right? And then it's gonna yield control of the event loop.

00:20:55.63
Chris Morrell
Now we're going to have another thing on the event loop that's just looking at that queue and waiting for things to be pushed into it. And when a request gets pushed into the queue, it's going to grab that request. it's gonna pass it to the Laravel application that's running inside of our test suite. Essentially, it's gonna run the code that this arrow get would run if you just run that inside of your test. um It's gonna pass that request to the the application, it's gonna wait for the application to return a response, and then it's gonna push that response into a different queue, and it's gonna yield control of the event loop.

00:21:37.31
Chris Morrell
um Now we have and another thing on the event loop that's looking at the response queue. Are you following so far? i This is complicated, but I feel like I need to get through the whole like chain of events um in one go.

00:21:53.88
Len Woodward
Yeah, I mean, i feel like I feel like I'm mostly following.

00:21:54.20
Chris Morrell
ah

00:21:57.12
Len Woodward
I don't think i don't think anything ever owns the event loop. um So one, we can have multiple event loops. We can do that as well. But everything in the event loop is just event listener based.

00:22:09.47
Chris Morrell
you

00:22:09.58
Len Woodward
So really, we're just looking to to have these different actions, emit an event with all the information that we could need. and then the other whatever it is that we're working with is gonna be listening for that event and then do whatever. So if we've got the browser that's got a listening event on um execute request, then like we send the we send the request out and then we we send the event out through ReactPHP instead of like interacting with the the browser directly or anything.

00:22:43.25
Len Woodward
um I feel like it's all making sense. It's, it's always a bit of a blind fuck playing with react PHP because one as as PHP devs we don't usually do a lot of like async await or, or event loop based code callbacks and I mean we're using more callbacks now than we were.

00:22:48.71
Chris Morrell
Yeah, it really is. Yeah.

00:23:01.76
Len Woodward
but it's It's a weird way to have to think, especially when you're writing the React PHP code, because you're basically defining all of the the behavior, and then you're triggering the behavior, which is always odd.

00:23:13.32
Chris Morrell
Mm hmm.

00:23:16.11
Len Woodward
um I mean, having worked in JavaScript land for as long as I did before my recent PHP clients too, like, I mean, I've been PHPJS together for like the last 15 years or whatever, so it's I've always been jumping back and forth. I feel like I'm better positioned to be able to work with these callback type things, event listener, event event loop, but it's always a bit of a thing with PHP.

00:23:42.94
Chris Morrell
Yeah, you know, I think maybe an easier way to like an easier way to kind of put all this in the in in a single mental model is like, just imagine four queues, right?

00:23:53.45
Len Woodward
Mm-hmm.

00:23:53.75
Chris Morrell
There's one queue that is, these are um these are ah commands going to the browser, right? And there's one queue that these are responses to commands that went to the browser coming back from the browser.

00:24:04.09
Len Woodward
Mm

00:24:10.07
Chris Morrell
Those are two queues, right? um And then there's two additional cues. One is these are requests that are going out and one cue that is these are responses that are coming in or vice versa, requests that are coming in and responses that are going out.

00:24:26.11
Len Woodward
-hmm.

00:24:26.44
Chris Morrell
And we have these like four queues, and we essentially have four processes sitting on the event loop that just sort of take control of the process, the you know come into the foreground, as it were, check to see if it needs to do anything to pop something onto one of those queues or to pull something off of one of those queues, and then gets goes back into the background. And then the next the next process, one of those processes is only dealing with sending out commands to the browser, The next one is only dealing with responses back from the browser. The third one is only dealing with HTTP requests that are coming in from the browser. And the fourth one is only dealing with responses that should be going back out to the browser.

00:25:08.72
Chris Morrell
And all of that is just happening on a constant loop where each one just says, all right, I'm going to wake up. I'm going to check to see if there's anything I need to do. I'll quickly do it and put, put any result of that work onto a queue and then I'll go back to sleep. And then the next one says, all right, now I'm going to wake up, check to see if there's anything I need to do.

00:25:23.87
Len Woodward
Mm

00:25:28.10
Chris Morrell
If so, I'll pull things off of the queue, I'll push things back on the queue, whatever I need to do, and then I go back to sleep. And each one of these just does that constantly in a cycle.

00:25:33.67
Len Woodward
-hmm.

00:25:37.35
Chris Morrell
And so even though each one of these things would traditionally be a blocking process, because we've split them up into each individual discrete chunks and are taking control and relinquishing control just for like a brief period of time, we can treat them as though they're all happening at the same time. I mean, maybe that gets back to your point of like today the difference between concurrency and parallel parallel processing.

00:26:06.57
Len Woodward
Yeah, I mean, I think that's making more sense now. So you've got your four queues and eat each of these things that has its queue is checking the, on each cycle or each tick of the event loop. Each one of those four queues is looking for stuff to do. If there's something there, it does it. And if it's not, it's it just lets the other queues check their work and just, we're doing this on every single cycle. So yeah, I i feel like there's a way to do this. i don't know I don't know how we would bridge the gap or kind of mimic the whole parallel testing story with like switching databases in real time because like there's going to be a reason they were doing that. they They wouldn't just duplicate these databases because anytime I run my parallel tests, it takes that database and then it makes 12 different versions of it.

00:26:53.44
Len Woodward
So they have to be doing that for a reason.

00:26:54.14
Chris Morrell
right

00:26:55.74
Len Woodward
So if we were to do something like that with our databases, swap out environment variables on the fly um as we're executing each of these different tasks. So each of these events that's getting dispatched to the to the event loop would have to know which instance it is that it's doing all this stuff for. So it knows which which headless browser to send it back to and then also which database that it's gonna use.

00:27:19.27
Chris Morrell
Right.

00:27:21.52
Len Woodward
so

00:27:22.31
Chris Morrell
Which would be perfectly

00:27:22.70
Len Woodward
There'd be some fun problems to solve.

00:27:25.17
Chris Morrell
I actually, now that I think about it, that doesn't seem as hard as it did the first time around because um for each parallel process, you'd also spin up a rack PHP web server. um And each one would just be listening on a different port. So we would just, you would just basically say, okay, requests that come into port, you know, 8801 are for the process that is connected to your test database one and requests that come in to port 8802 are associated with the, it with, you know, database two and so forth. So you could essentially just.

00:28:10.37
Chris Morrell
the The parallel story would be kind of the same as it is with the database. It's just we're running multiple instances of this web server listening on different ports.

00:28:23.04
Len Woodward
Well web servers usually do web servers usually run on multiple threads like so you can handle properly parallel responses or do they run on a queue so you're handling concurrent responses or requests because I feel like we'd probably still only need the one server instance because ah everything would just be on a queue.

00:28:36.85
Chris Morrell
ah

00:28:40.97
Chris Morrell
Well, just the way that ReactPHP works, I think it would make more sense to have, um because each of those is gonna be in a different memory footprint. So it just, it solves, it makes it a lot simpler to just have one, you know, sort of one ah event loop per parallel process.

00:29:02.40
Len Woodward
Oh, that's interesting. Yeah. i mean Like we said, React lets you have multiple event loops. Now, i don't know I don't know how they all interact when you've got multiple loops.

00:29:11.67
Chris Morrell
you

00:29:14.39
Len Woodward
I've only ever done work where everything is done on the main loop. So that way you're just doing like a loop, colon, colon, get, and you're just getting the one instance on the main one. But if we did have everything on its own event loop, I wonder what that would look like. And if it would be harder to manage or easier to manage.

00:29:34.10
Chris Morrell
I don't even think it's possible to do it the other way because when you do paratest, you're spinning up, you know, four, eight, 12, however many threads of separate processes. So each one of those is going to have its own event loop. They're not, they're not going to be talking to each other at all.

00:29:53.20
Len Woodward
Okay.

00:29:55.40
Chris Morrell
So, you you know, you would still.

00:29:55.51
Len Woodward
Oh, so that's probably why they were doing the multiple databases, not because of multiple transactions. It's because they're on separate processes.

00:30:04.02
Chris Morrell
Right. I mean, yeah, that I actually don't still don't know. Um, I still don't know the answer to, because even though they're on multiple processes, like you can have 12 separate connections to the same database, you know, like, um, but yeah, it must be that the block, like some, some of the blocking that comes up when you have multiple transactions open, you know, becomes a bottleneck.

00:30:20.31
Len Woodward
right

00:30:25.60
Len Woodward
Right.

00:30:33.79
Chris Morrell
I'm not sure.

00:30:34.12
Len Woodward
And computers are hard.

00:30:34.88
Chris Morrell
But the reality is we would we don't have to solve that problem. It's already solved by someone else, right?

00:30:40.86
Len Woodward
Yeah. oh Man, I think this would be a lot of fun to play around with I've got too many other projects. I don't have time I still haven't even been able to finish a proper integration with um With composer for a conductor because all my conductor stuff is being done through the process facade And I'm trying to use composer as a dependency now and you do all of that So I got other fun problems to solve but man this this has my brain going a thousand miles an hour I want to be able to do more stuff with react.

00:30:48.54
Chris Morrell
Yeah.

00:30:58.91
Chris Morrell
Mm-hmm.

00:31:08.61
Chris Morrell
it It is a very cool, I mean, you know, the the other the other thing that I've played with recently that got me thinking about ReactPHP is just, ah you know, I ah finally um built one of these SSH connected PHP prompt scripts, you know, like Joe has been doing.

00:31:10.54
Len Woodward
It's too fun. I

00:31:34.39
Len Woodward
oh one of joe's twoiess

00:31:36.28
Chris Morrell
Yeah. and um You know, essentially that's on an infinite while loop. um And. so A particular implementation decision ah meant that I'm actually executing a new prompt like for each cycle of the interaction.

00:32:00.48
Len Woodward
Oh, one of Joe's twoies?

00:32:03.13
Chris Morrell
right So you'll you'll essentially like be prompted to do something, and then that prompt will exit ah inside of while loop, and then a new prompt will but get run. um

00:32:17.48
Len Woodward
like a same instance of the same prompt just with different

00:32:21.14
Chris Morrell
ah No, a new instance of the same prompt.

00:32:21.57
Len Woodward
i

00:32:23.14
Chris Morrell
It's like, it's stateless. It just, it, every time it just starts anew. um because it's it's essentially it's essentially mimicking a ah ah shell. um It's this fun thing. if you If anyone hasn't seen this, just do ssh endless dot.directory and you will get a um ah shell that has basic commands like a cd and pwd and ls and you can cat files. um and it And all it is is you can,

00:32:57.65
Chris Morrell
Essentially CD into any directory you can imagine and then cat any file that you can imagine would be in that directory. And it will give you that file back using open AI. Um, but I didn't want to have to think about like scroll back and all of that stuff.

00:33:09.51
Len Woodward
Hm.

00:33:17.39
Chris Morrell
So I just thought, okay, when I type like PWD. I want the prompt to handle all the the um keyboard input and all that stuff. But once I print out the working directory, ah that prompt just ends. And now a fresh prompt starts. um And that way, as you interact with the shell, um your history is just handled by your terminal. We don't have to deal with with history.

00:33:52.59
Chris Morrell
Um, and it made me think like this would make sense to be inside of, uh, an event loop rather than just a while statement.

00:33:52.60
Len Woodward
Hmm.

00:34:00.55
Chris Morrell
And there might be some interesting, um, opportunities, especially around like, um, You know, those HTTP requests to open AI take a little while. So like when you are um opening a file that no one has ever opened before, I have to make that request. I can't just use the cache. And it would be nice if that wasn't totally blocking. Like you could do that and we could render, even if it was just like an animated loading indicator or something like that.

00:34:37.83
Chris Morrell
and And I know that that can be done with process control a little bit, like that's how the spinner process works in prompts.

00:34:43.83
Len Woodward
Yeah, I, I tried to re implement the spinner in, in this new async prompt of mine. I didn't exactly finish it, but like the way they, all the stuff they had to do to get that to work, it just feels so gross.

00:34:50.32
Chris Morrell
Interesting.

00:34:59.84
Len Woodward
Like props to them for making it work, but holy shit. It, it's not

00:35:04.83
Chris Morrell
It is, that is mind bending, the ah process control forking where it's just like, you just have to, you have to like decide what to do based on what what process ID you're in.

00:35:19.91
Len Woodward
Yeah, and I mean, it makes sense because you can't necessarily do that in an async way because you don't know whether or not the the function that it's running is blocking or not. So you can't be triggering a re-render on this. So, I mean, there might be a way to do it better where we just fork the process once and then we're doing that process on a loop to re-render the thing and just a little bit more clean.

00:35:46.54
Chris Morrell
Mm-hmm

00:35:46.59
Len Woodward
um I might um might pick that up again. But um yeah, the other thing like streamed streamed output and stuff like that, instead of spinning up another prompt instance, that's that's one of the things that I did in the prompts with my async prompt implementation. So we can trigger a render at any point. You pass a callback into it and as you get the the output, from the other thing, then you're triggering a new render.

00:36:14.92
Chris Morrell
Mm-hmm

00:36:15.08
Len Woodward
um And also let me do other things as well where I've got a debouncer built in. So now I've got like a Scout powered search endpoint. And instead of sending out a search request on every key press, it debounces for like 300 milliseconds. And we don't really have a way to do that in prompts the way it's built right now.

00:36:30.36
Chris Morrell
Mm-hmm.

00:36:37.96
Len Woodward
So aye i um I submitted, um I've had like five PRs go into prompts to try to get these projects of mine moving forward.

00:36:38.13
Chris Morrell
That's interesting.

00:36:46.33
Len Woodward
And I had like this one final PR that was like my last compatibility PR where I take the looping mechanism and I extract it into a separate ah protected function so that we can extend that and override it.

00:36:58.65
Chris Morrell
Okay.

00:37:01.02
Len Woodward
um The only thing is technically, I think it might, I think it could be a breaking change because when we're setting the output for the terminal, I'm setting, I changed it to static output instead of self output. And I haven't had the opportunity to check this to make sure to to see whether or not it actually is a breaking change or not so they ended up closing the PR. I'm gonna, I'm gonna buy Taylor a beer in Dallas and see if we can reopen that because with this.

00:37:32.17
Len Woodward
With this final PR, it allows me to have this really, really minimal implementation on a new abstract class that extends the original prompt. And I just overwrite a couple of methods and then put my ReactPHP looping mechanism in there and then overwrite the the testing trait. And yeah, so if we could get that final thing in there,

00:37:50.53
Chris Morrell
Yeah, that's interesting. That's actually really interesting, yeah, to to think about, you know, now that there's there is ReactPHP and there are other there are other asynchronous PHP libraries out there that use a similar principle. um I'm blanking out on on the name of the other one. It's got like a lightning bolt logo.

00:38:15.08
Len Woodward
Are you thinking about the PHP extensions like um that React is built to be able to use, like libev?

00:38:18.11
Chris Morrell
No, no.

00:38:22.44
Chris Morrell
Now, there's um there isn't there was a project that came out right when fibers first came out that was essentially um you know trying to be a really low level abstraction above fibers. you know Still low level, but a little bit higher level than fibers. um and it It implemented its own event loop, I'm pretty sure, but I'm blanking out on the name of it. um I'll add it to the show notes if I can find it, but um it would be interesting in prompts to to essentially be able to say, use this event loop implementation, right?

00:38:48.76
Len Woodward
Yeah.

00:39:01.92
Chris Morrell
The default prompts event loop implementation is just a while loop, but um like you could make that fully extensible without it really costing a whole lot, right? You just extract the code that runs inside of the loop into a separate call ball, right?

00:39:18.28
Len Woodward
yeah

00:39:24.89
Chris Morrell
And then, yeah, that's really clever.

00:39:25.09
Len Woodward
Yeah, that's literally all I did with this. is um So because I had to change that that call to from self-dollar output to static dollar output, I think that's why they they closed it. Because and they're working on a whole bunch of crazy new shit that they're going to announce. So they're all heads down. They don't have time to evaluate this and make sure that it's not going to break anything. So I understand why they closed it. um But yeah, that's all I did. I took out the while loop, put that into a new function um so that I could then overwrite the the output because I can still use buffered output in tests, but I've got my async output, which just uses the ReactPHP streamable writeable interface.

00:40:09.82
Chris Morrell
Mm

00:40:09.83
Len Woodward
And then I've got the streamable readable interface. So I can do all of those things asynchronously. um And then yeah, we just, we overwrite a couple of those things. The funny, what are the funniest parts of that PR though? It just, it feels so dumb that I had to do this. um And it's nothing to do with the way they wrote it or the way that I wrote my thing. It's just the the nature of prompts because even though in prompts, you don't really have any prompts that are capable of returning null.

00:40:36.04
Chris Morrell
hmm

00:40:40.100
Len Woodward
But null is a valid return type and in some of my prompts like I had a video a while ago where I did a tab scrollable select. So it gives you some tabs where you can scroll back and forth that automatically scrolls and then all of that stuff you can select your thing and it gives you the value, however.

00:40:49.03
Chris Morrell
Mm hmm.

00:40:57.26
Len Woodward
In my implementation, you can hit escape and it'll return null. And the way that I was using it was if you return null, then you hit a go to statement and it goes back to to give you new options and you go through the same thing again.

00:41:08.17
Chris Morrell
Hmm.

00:41:10.41
Len Woodward
So null is a valid return type. um So in their loop. um Like if it knows to keep on, it knows to keep on looping given a certain, um given certain results or whatever. But once we extract that looping mechanism, if we return null, whereas it would normally just continue the while loop, the function, like the whole loop has already executed. So what I had to do is if you,

00:41:45.63
Len Woodward
if you return If the function returns without the looped thing having returned null, it returns an instance of a class called nothing. So I've got prompt slash support slash nothing. And in that render method, it says, if if this arrow run loop equal is instance of nothing, then run loop again or whatever.

00:42:10.58
Chris Morrell
Hmm.

00:42:11.25
Len Woodward
This is just is really funny that I had to do this.

00:42:11.86
Chris Morrell
Yeah. That's funny.

00:42:15.29
Len Woodward
It just it felt really dumb, but it the solution looks really clean if you look at it and the way it gets used.

00:42:20.12
Chris Morrell
Hmm.

00:42:22.33
Len Woodward
So it's kind of like in in Go or whatever, when you're checking for nil or whatever.

00:42:22.45
Chris Morrell
Hmm.

00:42:26.70
Chris Morrell
Yeah.

00:42:26.77
Len Woodward
like so's Yeah, there there were some problems I had to solve. I think I did okay, but it's not it's not just archer levels of code beauty.

00:42:35.25
Chris Morrell
Yeah. It's a tough one.

00:42:40.18
Chris Morrell
prompts is wild. I mean, I've only looked at it a little bit and just when I was pairing with Joe, so he he kind of knew what he was talking about and could steer me away from making all the terrible mistakes that I was about to to make. um But it is, I mean, it's it's such an impressive tool and it's such a it's such a straightforward um I mean, essentially, prompts is an event loop in a state machine, right?

00:43:06.80
Len Woodward
Mm

00:43:07.34
Chris Morrell
And ah and are and a renderer, like that's that's what it is, is just ah you've got an event loop that executes some thing that updates the state of your state machine and then a renderer that ah renders based on the current state of your machine.

00:43:11.04
Len Woodward
hmm.

00:43:27.61
Chris Morrell
um And that abstraction is

00:43:28.91
Len Woodward
I really think it's an interesting rendering paradigm too. It's like the difference between Vue.js and React.js because every single loop of the render function, you're blowing up your whole terminal output and you're re-rendering the whole thing from scratch. You're not going in and targeting certain areas.

00:43:45.47
Chris Morrell
hu

00:43:47.77
Len Woodward
So it's it's an interesting paradigm and that was really fun to see and experiment with.

00:43:52.38
Chris Morrell
Well, it's interesting. I talked with Jess. I think we talked about this on the ah podcast. She originally implemented it ah with like a diffing algorithm. Um, but found that doing full re-renders was faster and, and like reduced or was as fast and didn't have any of the bugs that they had to deal with with the diffing algorithm. So originally it was, originally it was trying to just change the areas that had changed, but, um, it just turns out that that didn't really matter, which is funny.

00:44:17.51
Len Woodward
Yeah, it makes sense.

00:44:30.31
Len Woodward
Mm hmm.

00:44:32.44
Chris Morrell
I mean, maybe on a super old, super, super old computer, it would have made a difference. I'm not sure if the ah processor speed.

00:44:38.77
Len Woodward
Yeah, maybe on my parents 386.

00:44:39.69
Chris Morrell
know

00:44:43.18
Chris Morrell
ah All right, this isn't ReactPHP, but you brought um He brought conductor up and I did want to just like talk about it briefly because I think it's such a oh ah lovely idea. um And I know I talked about it on the podcast before. I know that, um ah you know, folks have talked about this on Twitter. Wouldn't it be nice if there was a. composer version of NPX, right, something where you can just execute a composer package binary without actually having to have that um installed into your project or globally installed. like And, you know, those are the types of things where

00:45:24.42
Chris Morrell
this is ah This is something that you're only gonna have to do once to create a new project, and you might never need to run this command again, or it's like, you know, with Tailwind, they have a npx command to set up a new Tailwind project.

00:45:27.18
Len Woodward
Mm hmm.

00:45:38.32
Chris Morrell
It's like, you don't need to have this running on your machine. It's just to get you some stuff into your app's directory. um But having the same thing for Composer would be so cool. um And it would open up a lot of possibilities. And so you've been working on conductor, which is a tool to do exactly that.

00:46:01.65
Len Woodward
Yeah, it's fun I found a couple of fun use cases so like for a project where maybe you want to try PHP stand but you haven't, you can always just like run it and see if it passes at level five, or you can try running it at level nine. You can see what files would get changed if you do a dry run. um I've done the same thing for some rectors one really useful use case is Is jmax? Laravel shift cli so he's got some of these automated shifts where like you can run this thing Through conductor and then you can remove all the dock blocks from your code or you can run certain things where like

00:46:41.07
Len Woodward
I think this one's a rector instead, where if you' if you're just doing a one-line function returning it, it'll change it to a short function. um So you've got all these different little things where you don't need to have this thing installed in your project. And you don't need to have it installed globally, because it's run so infrequently that the next time you go to run it, it's probably going to be out of date. And so the proof of concept that I whipped up, it's really, really simple. Like, I'm just using the process facade, the things built on Laravel Zero, so you get all of those affordances. So I just use the process facade, run everything in TTY mode, so if there's some sort of interactive interactivity that you need, you can do whatever you need to in it, and it's not gonna block and hang.

00:47:25.01
Len Woodward
um And then that got a bunch of conversations going like it got pretty popular for a tweet for someone with a smaller following is mine. But Roman got in touch with me from the PHP foundation because he's trying to build a tool called PHP up. He had a talk at PHP UK recently where he was talking about comparing the PHP ecosystem to all these different programming ecosystems. So you've got like Go, which has a really tightly integrated ecosystem, everything like the static analyzer, everything is built in, and then stuff like Rust.

00:47:50.53
Chris Morrell
Mm hmm.

00:48:00.05
Chris Morrell
Yeah, even the like formatting.

00:48:02.79
Len Woodward
Yeah, yeah, and then the same with Rust, and Rust has Rust up. So he's trying to develop PHP up, or fup. But um he he was trying to do this thing where he packages up the PHP binary, and then he ships it so that you don't need to install anything, just everything comes with it. And he was bundling Composer in with that, but he wanted to be able to run a bunch of these Composer packages. Do not disturb mode has been turned on. Now the freelancer chat's going nuts. But he wanted to be able to run these, all these different tools like PHP, Stan and rector and all of that, but he didn't want to have to be bundling all these different things. So he was one of the people that wanted a tool like conductor as well. And so he got in touch with me, we chatted a little bit and he sent me an elephant.

00:48:51.33
Chris Morrell
Very cool. I like it. It's a black and pink elephant for anyone who's listening.

00:48:56.79
Len Woodward
Yeah, it's got trolls hair. and it' so It's pretty dope, fits in with my other elephants nicely.

00:49:02.56
Chris Morrell
Love it.

00:49:02.67
Len Woodward
But yeah, Roman is just a super fucking clever guy. And we chatted for for a while. We were just talking about different RFCs, why certain ones might have gotten closed, not gotten passed. We talked about the history of PHP. We just chatted and nerded out for a bit. We looked at his project. We talked about what conductor might look like and how to get it set up so that we could bundle it. And so instead of taking the process approach, what I'm going to take instead is use Composer as a dependency and try to use all these different commands within there. So I'm not executing any of the binaries myself. I'm going to be I was already deferring to Composer or yeah Composer exec.

00:49:44.19
Len Woodward
package name or a composer global exec package name, which has the drawback of flags, like passing flags is just pure violence.

00:49:45.57
Chris Morrell
Mmhmm.

00:49:53.80
Len Woodward
So if you're trying to run Som, you have to do like um conductor run Vimeo slash Som dash dash space dash dash space dash dash dry run. because every every set of dashes is going down to the next process. So if I was calling this, calling the binary directly, I would only need two sets of dashes. But because I'm going to composer exec, that's another set of dashes.

00:50:21.90
Chris Morrell
Hmm.

00:50:22.26
Len Woodward
so So I'm gonna try to just use these commands directly, which has the added benefit of it's gonna make it easier for Roman to bundle up into PHP up because you're not using the process facade, you're you're using the code. The other benefit is that I could then take this system of mine and I could PR it to Composer and it wouldn't be a break and change.

00:50:42.77
Chris Morrell
Right.

00:50:43.78
Len Woodward
So instead of them having Composer exec, we could have Composer run. And then we can have all of the same functionality.

00:50:48.47
Chris Morrell
Yeah.

00:50:50.22
Len Woodward
We don't have to wait until version three then. this could Like this could ship as soon as I'm done. And so Ed's got relationships with some of the the people that are deep into the composer ah contributors. And so we're gonna see if this is even something that they'd be open to having. But if they're not, we can always just have it be like a plugin or extension. or a separate binary that ships. So we got a lot of different abilities there, but again, the the composer code base is pretty broad as well.

00:51:13.08
Chris Morrell
Mhm.

00:51:20.27
Len Woodward
So I'm trying to go through that and figure out exactly how I'm tying into this, how it's calling all these different things.

00:51:20.36
Chris Morrell
Yeah.

00:51:26.01
Len Woodward
So I don't have a whole lot of time to work on it just because of all the other shit I got going on. But I'm really excited for what this could look like in the end. Like I haven't even properly announced composer or conductor. I've just teased it and like people are already using it. It's got it's got stars on GitHub and like I've never actually linked anyone to it.

00:51:42.35
Chris Morrell
That's so awesome.

00:51:48.29
Len Woodward
So they're just finding it.

00:51:50.65
Chris Morrell
That's wild. Yeah, that's pretty cool.

00:51:51.85
Len Woodward
Yeah, so that was kind of funny.

00:51:53.81
Chris Morrell
Is this the first, um, is this the first composer plugin that you've written?

00:51:58.77
Len Woodward
I haven't started doing it as a plugin yet. I'm just trying to make the code work and then I'll probably try to take the plugin route afterwards.

00:52:00.90
Chris Morrell
Okay.

00:52:05.16
Chris Morrell
Mm hmm.

00:52:05.41
Len Woodward
I don't know if doing it as a plugin is gonna be the right move or trying to PR it directly to the composer. So we'll see which route it takes.

00:52:13.27
Chris Morrell
I think generally they'll be the same, mostly the same implementation though, because you're going to be working with the same APIs, whether it's coming from inside of composer or inside of a plugin, I don't think makes a huge difference.

00:52:18.40
Len Woodward
Mm-hmm. Mm-hmm.

00:52:27.56
Chris Morrell
As far as I know. Yeah, I had, this is a thing I've been meaning to pick this up again, because I. I think when Composer 2 came out, it broke it. and I didn't use it enough to really care, but I had a Composer plugin that I wrote that all it did was it kept track of what Composer commands you've run on different branches of a project.

00:52:57.58
Len Woodward
Hmm.

00:52:57.61
Chris Morrell
Because, and and I'm curious if this has ever happened to you, because I'm not sure if this is a widespread problem, but in my case, if I'm working on a branch that, you know, lives for a little, little while, um, where I've, uh, you know, installed some different dependency or changed a version of a dependency or something like that. every single time that I pull in changes from production or um yeah I just introduces a bunch of merge conflicts because the composer lock file.

00:53:30.38
Chris Morrell
um

00:53:30.75
Len Woodward
Mm hmm.

00:53:32.80
Chris Morrell
where the only change is really that I did composer install whatever or composer update one particular package. um And so I always thought it'd be nice to just be able to inside of a branch, just run some thing that's like just replay the ah composer commands that have run on this branch since the last time. And that way I can just revert the composer that.lock file and essentially reinstall whatever I installed fresh without having to deal with merge conflicts or rebasing or anything like that. um And it was kind of interesting to build, to just to play with the Composer APIs and the plugin ecosystem, because I don't see much of that. But there are interesting things there. It's it's pretty cool.

00:54:25.47
Len Woodward
Yeah, so I've definitely run into that issue. um usually what i I'm usually like a merge in, merge out kind of guy. um So I'll usually just nuke the composer dot.lock and then just composer update and then commit that.

00:54:34.22
Chris Morrell
Mm-hmm.

00:54:41.80
Len Woodward
um I don't necessarily like doing it that way just because you're doing more

00:54:44.16
Chris Morrell
Mm

00:54:47.95
Len Woodward
you're doing more in the merge commit than you're actually merging. And I don't always like doing that.

00:54:52.84
Chris Morrell
-hmm.

00:54:53.98
Len Woodward
So what I've started doing recently is I'll actually do a proper rebase. So we get that change composer.json and composer dot.lock from main. It gets to the conflict. Then I just look at the packages that I installed, run that again, then run composer update. And that's the new commit. And then we just replay the rest and it just works out fine.

00:55:15.36
Chris Morrell
Mm-hmm Right

00:55:15.75
Len Woodward
um but I am having to rerun that proper update command manually. So I don't know if a ah plugin would would solve that for me, but i it's not generally a problem that I run into terribly often.

00:55:32.04
Chris Morrell
I mean, that's the reason why I never kind of picked it up again is it's like it happened. I wanted to sort of experiment with the composer plugin. So I use that as an excuse, but once it broke, I didn't really have much of a reason to rebuild it because I, you know, I didn't get enough utility from it.

00:55:52.17
Chris Morrell
Oh, well, other than our ah brief ah briefef hiccup in the middle there, this has been a great chat. Is there anything um anything else that you were hoping to get to? um Anything, any other ReactPHP stuff or anything else on your mind before we call it?

00:56:08.33
Len Woodward
No, I think we touched on most of it. No, I think we got to all of it. um Yeah, I'm about to hop on a call in like two minutes with Eric Barnes so we can chat about whiskey because I got all these products.

00:56:19.75
Chris Morrell
Nice.

00:56:21.86
Len Woodward
I didn't actually realize that Verbs was using whiskey. So I'd be interested in knowing what you do and don't like about that. So that would be another fun chat to have briefly.

00:56:31.95
Chris Morrell
Sure.

00:56:32.29
Len Woodward
I don't know if I have time.

00:56:32.31
Chris Morrell
I mean, it's just, uh, it just works. I don't have to think about it. That's, that's what I like about it.

00:56:37.88
Len Woodward
Is it easier for you than Husky? That was my goal.

00:56:41.43
Chris Morrell
Uh, yeah, I mean, it works.

00:56:41.68
Len Woodward
Is it better than Husky?

00:56:47.75
Chris Morrell
Uh, I, I feel like I ran into issues with, um,

00:56:54.83
Chris Morrell
I don't think we were using Husky before. I think we were using like some other thing called like composer Git hooks or something like that. Um, but it had a bunch of version incompatibility issues. And, uh, so I switched to whiskey because, uh, that, that felt like the best option at the time, but, um, it's, you know, it's one of those great tools where you don't have to think about it. ah you know

00:57:23.53
Len Woodward
I've tried to build it that way, yeah. Like the biggest thing that I did is um because recently I changed a major architectural thing. yeah I'm just gonna send a quick message to to Eric saying that I gotta push this back like three minutes. ah Be there in five. just finishing up another pod with Chris.

00:57:45.78
Chris Morrell
Hmm.

00:57:48.10
Len Woodward
So yeah, I changed something architecturally recently where um I had to do this really nasty thing where inside of the git hook, I was registering a a call to a shell script and then the shell script would call ah my Laravel zero app to get a list of the hooks that should run but it could also skip the hooks if you needed to based on like an environment thing. You know, if you do dash dash, no verify. um But there's some actions that don't support that.

00:58:19.67
Len Woodward
So you can just do like whiskey skip once and then you can like finish emerge and go dash dash continue and it's not gonna run the hook.

00:58:25.60
Chris Morrell
Oh, interesting. Oh, that's cool.

00:58:27.90
Len Woodward
um So as doing that, the the reason I had to do this was just so that we could get the proper terminal output but colors weren't being maintained. It was just really ugly. um So instead, now we're just calling whiskey directly and we're doing everything through the process facade using TTY mode. So you maintain interactivity, you maintain terminal colors, um but in order to do that, that means that the command that's saved in your GitHub changes.

00:58:44.94
Chris Morrell
Oh, interesting.

00:58:53.25
Len Woodward
So if you update the version of whiskey, that thing isn't going to exist anymore. So when you install whiskey, whiskey update ah will run, like it still has that command. but it just routes that to whiskey update, updates the new git hook so that it uses a new system, then calls a new system, and then it all just kind of works. So I've done this thing specifically so that when you change when you change something within whiskey, it'll update itself in a seamless way.

00:59:23.31
Chris Morrell
Yeah, that's awesome.

00:59:23.41
Len Woodward
So hopefully that works for everyone.

00:59:25.83
Chris Morrell
Yeah, I mean, it's it it's been it's been solid for us, for sure.

00:59:30.05
Len Woodward
I'm going to be repeating all of this in like the next 20 minutes.

00:59:30.45
Chris Morrell
um Well, I know you got to go.

00:59:33.94
Chris Morrell
Is there um is there anything that you would like to um specifically call out, promote, hype? so And also, where do people find you on the internet?

00:59:46.08
Len Woodward
Yeah, ah so my project gopher, like at project gopher is pretty much my handle everywhere. Project is spelled with a K instead of a C. um So find me on GitHub, sponsor me. um You can find me on YouTube now under at artisan build, because that's the technical co-founders as a service agency that Ed and I are starting. Yep, Twitter is at Project Gopher. Pancreas at Project Gopher. LinkedIn at Project Gopher. Find me on all the places. um Find me customers. Find me clients. Find me work. Give me sponsorships. Give me stars on GitHub. That's the one that I really want. I just want the warm and fuzzies from from knowing people use my work.

01:00:30.25
Chris Morrell
We don't care about money. We care about stars.

01:00:33.15
Len Woodward
Yeah, we just want credibility.

01:00:36.03
Chris Morrell
For sure. Well, this has been really fun. and Um, thanks for, thanks for hanging out.

01:00:42.58
Len Woodward
Yeah, thanks for having me on. It was a lot of fun.

Creators and Guests

Chris Morrell
Host
Chris Morrell
Father of two. Mostly talking about PHP/Laravel/React on Twitter. He/him.
Len Woodward
Guest
Len Woodward
Founder of ProjektGopher Multimedia
ReactPHP + Event Loops w/ Len Woodward
Broadcast by