[00:02] about PHP8.4's lazy objects RFC. This is a very technical feature that uh has a lot of moving parts. So on [00:14] in this video I'm going to make an surface level introduction to it. Uh using doctrine RM as an example. We are going to look into how example. We are going to look into how doctrine RM uses lazy objects and uh how [00:30] it benefits from it going forward. So what is lazy loading? Lazy loading is essentially deferring the constructor of an object until the first [00:43] constructor of an object until the first time the object is being used. So it means that the constructor of the object is not being called until you actually start using the object. If you don't use the object then the [00:59] constructor will not be called and in case of doctrine ORM the object will not be loaded from the database at all. However the object is already present in the code in the object graph. So it can be passed around [01:15] um to the different parts in the system. It can be passed to a template. It can It can be passed to a template. It can be passed to a a method uh performing logic. And as long as you don't access any of the properties of the lazy [01:30] object, it will not be loaded into memory. And uh therefore it saves a lot of resources. Let's dive into this uh RFC a little. So [01:44] the RFC has been proposed by Ano Leblanc and Nicolas Grias and uh especially Nicholas has been working on the concept of lazy loading and lazy proxies in [01:56] of lazy loading and lazy proxies in Symphfony as well. So um doctrine had its own implementation for lazy loading proxies uh since version lazy loading proxies uh since version two um contributed in [02:11] two um contributed in 2012 and uh recently switched to the Symfony implementation um using the Symfony var exporter component which Nicolas wrote. So, Nicholas is very [02:24] knowledgeable about proxy objects and knows a lot of the edge cases and he's uh the domain expert that contributed to uh this RFC and Ano is the engine level um expert uh who is also working for the PHP foundation uh on on PHP as a [02:42] language. So, this RFC was accepted and added to PHP 8.4 for and we are going to look how it works um based on the doctrine doctrine example. Let's uh get a little deeper to [02:56] uh some code examples to understand how it's working. So this RC adds uh new functions that you can call on um the reflection class. So it's a reflection level feature meta [03:11] programming and in case of doctrine we are using the new lazy ghost method and are using the new lazy ghost method and lazy ghosts are objects that are lazy ghosts are objects that are um the real object and deferred [03:27] constructor loading until the first um property is accessed. um property is accessed. This is done using the initializer um call back. So you pass the initializer and the initializer is [03:42] responsible for loading the object into its state and calling the not going into in this video because it's uh against um uh very technical RC. [03:59] it has its own in-depth things that we need to look at and maybe the topic of a different video later. So the way you are using it is [04:11] later. So the way you are using it is you have an initializer for um your you have an initializer for um your class. you call uh you instantiate a new reflection class and then you call reflector new [04:23] lazy ghost and then you have an instance of my class that's not initializ it yet of my class that's not initializ it yet and initialization is triggered by a few and initialization is triggered by a few different things let's look at this RFC [04:37] different things let's look at this RFC into the initialization triggers when is an object triggered uh and when will lazy loading be triggered Uh this section lists a few of the cases. So reading or writing a property, [04:51] testing if a property is set or unsetting it, calling reflection unsetting it, calling reflection property, get value, set value or the PHP 8.4 four equivalents for property hooks. Get raw value um and set raw [05:05] value calling reflection object get properties get property cloning the properties get property cloning the object and uh this all causes initialization. What is initialization? The co the initialization call back is [05:20] The co the initialization call back is called. So here what we see here this called. So here what we see here this call back is called and uh afterwards call back is called and uh afterwards the object is marked as [05:37] example that I've prepared before to see how this actually works. how this actually works. I have prepared a little project and it I have prepared a little project and it has a post entity in [05:51] doctrine and we are using the doctrine 3.4 u um development branch because the integration with this lazy loading uh RFC uh is not uh released [06:04] yet. So um the post entity has just an ID and a message and a reference to an author. So it's very straightforward. Um and we are using a small script as an example here to see how this works. So [06:21] in doctrine uh the method get reference on the entity manager creates um an a lazy reference of the post object. [06:37] So the way this works is uh we have the get ID method and the get message and now we're trying to see how lazy loading works under the hood. Let's look at this um using a profiler so that we can see the [06:53] the um runtime behavior of uh the proxy RFC and lazy objects feature. And uh And uh I'm a huge fan of trying to understand [07:05] code not only by reading the code in the IDE but also running it um and in a profiler understanding the uh the the trace flow. So let's look [07:18] uh the the trace flow. So let's look at calls to the function uh two um calls to the two um methods on the post object. So we had get id and we had get object. So we had get id and we had get message and um the output of the [07:34] script prints the ID and prints the message here. So how does lazy loading come into play for the proxy? So that's play for the proxy? So that's um easy to see in this case because [07:50] um easy to see in this case because uh get ID is called once and uh is very fast. uh get message however takes unreasonably long for unreasonably long for um uh a simple getter. And it's uh easy [08:03] um uh a simple getter. And it's uh easy to see why because uh as you can see here uh get message has a child function closure proxy factory get proxy and you can see below it calls the database at [08:19] the end and um also connects to the database. So post get message the rendering of the message is triggering the lazy loading. And now let's try to find out how that works and why the lazy losing wasn't [08:35] works and why the lazy losing wasn't triggered by calling the get ID method. So uh we use proxy factory get proxy line 173 as an entry point to uh [08:47] understand this. And going back to the IDE, we are And going back to the IDE, we are searching for proxy searching for proxy factory and trying to understand that [09:08] 173 is what we are looking at. So we can see here uh entity manager get configuration is native lazy objects enabled is checked. So this is a new enabled is checked. So this is a new functionality and I have called this on [09:22] the doctrine config object enable native lazy objects with um doctrine 3.4 we are going to release the old and the new proxies side by side and you can opt into them. Uh the reason for this is this feature requires 8.4 obviously not [09:39] this feature requires 8.4 obviously not uh all users of the ORM3 are using it. Um but also since it's a completely different implementation, we want users to opt into it and test it on their applications before moving. However, our [09:52] goal is that we are removing the old way of generating proxies in doctrine 4 when we increase the requirement to PHP 8.4 for and our the benefit we will [10:06] gain from this is that we can get rid of the code generation proxy approach and also all the engine level hacks that we are using to implement um proxy are using to implement um proxy generation at the moment. So lazy native [10:21] lazy objects are enabled. We are looking at the proxy factory. What does it do? It calls reflection class new lazy ghost. That is reflection class new lazy ghost. That is what we saw in the RFC. Uh it creates an [10:36] initializer here and the initializer essentially does only one method call. essentially does only one method call. It calls entity persist load by ID using an identifier and an object. The object is [10:51] passed to the initializer and the identifier is bound to this closure as a use variable. So we are keeping the identifier for this proxy uh inside the [11:05] identifier for this proxy uh inside the initializer and when the initialization is called we use this to um call the load by ID method on the entity persistor the entity persistor is a doctrine API low-level API that loads an [11:22] object from the database and when the second object uh is when the second argument is passed passed as an object, it will use that object as a reference it will use that object as a reference and populate the properties of [11:41] need uh for this feature to work and nothing more. So it's really just using new lazy ghost uh this initializer. We already have this load by ID method available um in the doctrine API. we didn't need [11:57] to implement anything new and this essentially worked out of the box without many changes. So this still this doesn't answer the question why the method called post get ID didn't trigger lazy [12:13] called post get ID didn't trigger lazy loading. So to remind you um we have post get message triggering it. Post get ID didn't trigger it. And uh if we look ID didn't trigger it. And uh if we look back at the code, post get id is called [12:28] first. So it should have triggered it uh given that it was uh printing the value already. So what is this proxy magic at [12:41] place there? So all magic is there to be understood. That's actually very simple. Uh doctrine has an API called property accessoruses. uh property accesses API [12:53] we had to add um to support property hooks and doctrine and it also enabled us to support proxy objects and we will see for um why this is the case um in a [13:06] few seconds. Let's go down the rabbit hole hole here. Property accessor set value. [13:19] So let's go into that. It's an interface and we have a few implementations. So what we could do now is look at the profiler which implementation is [13:33] used raw value property accessor set value and type no default property accessor set value. Those two are called. So let's look at both of [13:57] accessor is um a delegate. So it calls another property accessor in set value and this is what's happening in our case. So let's look at the second [14:09] implementation raw value property accessor. And the set value method of this one is what we are looking at. We will we see set raw value without lazy initialization. So what does that mean? [14:25] initialization. So what does that mean? Uh going back to the RFC, we talked about initialization triggers and the lazy objects RFC comes triggers and the lazy objects RFC comes with a few features to work around lazy [14:39] with a few features to work around lazy loading. uh that means we can set a property to a value already without triggering initialization and then if we read from this [14:51] property the value is already present and initialization doesn't have to happen. So this is what's happening here. Uh we see here the following special cases do not trigger initialization of a lazy object. Set raw [15:06] value without lazy initialization. So let's look again in the profiler if this method is being called. Set raw value without lazy [15:19] called. Set raw value without lazy initialization is being called. So initialization is being called. So here the lazy initialization is um circumvented. This value is set on the property on the on the entity already. [15:33] property on the on the entity already. And um this is why calling post get ID does not trigger lazy loading. Uh let's go back to [15:47] loading. Uh let's go back to the code example to try to understand it the code example to try to understand it again. We load a post object from the database. Get reference. No, we don't load it from the database. We create a [16:02] lazy object for this post entity with ID 1 inside the proxy factory. Get proxy is called for the post class with the identifier. It's [16:15] already a known information. The identifier for an instance of an entity. This known information is then used to populate the property. And this [16:28] used to populate the property. And this is why it doesn't trigger when get ID is called. Uh it's getting called when get message is called because this is not available yet. Initialization is triggered. The entity is loaded. Both [16:42] get message and also the author are being populated at that point. So let's play around with the proxy uh script a little bit more. Let's add echo for the [16:54] author ID. and again run this with the profiler. So here we see post ID message hello world one author ID 1. So this worked. Let's take a look at what's [17:11] happening here. In the overview we can see again um a doctrine load for post uh but we don't see a doctrine load for the author. Let's look in the call graph [17:25] for all calls to the user object user get ID is being called it doesn't have get ID is being called it doesn't have children um child calls. So um again here since the uh identifier is known while constructing the proxy a set raw [17:42] value without initialization is used and reading this value back from the object does not trigger lazy loading and we can print the ID of the user without loading [17:54] the object from the database. So let's take it a little um uh one step further. Let's print the name of the author and [18:16] then the name of the author. And now if we look at the profile we see there is a we look at the profile we see there is a doctrine call to a user to load the user entity. And we see also a query on the user [18:30] user table. And uh looking again at the user uh get name has a runtime that is quite large for uh just a getter. And looking at this we see again here a call to the [18:47] proxy factory. Um and we see the proxy factory is called from two different locations. post get message. We already saw that before. And now from user get name. So both of these [19:02] loading and um load this information into memory. So thank you very much for watching this video. I hope you liked um [19:15] watching this video. I hope you liked um my introduction into lazy loading uh and my introduction into lazy loading uh and the lazy loading feature in PHP 8.4. If you want to consume similar content like this, uh please um uh subscribe to this [19:29] this, uh please um uh subscribe to this channel and uh continue watching uh and looking out for my content that goes deep into PHP performance. Uh in next version of this um series, I will look into the lazy proxy equivalent of the [19:47] RFC and explain what you can use that for. And I hope you got a good insight for. And I hope you got a good insight into lazy objects and how doctrine works and maybe also got some ideas how you can use this yourself in in your own [20:01] code to improve performance by simulating the availability of objects without uh loading them at the time of construction. instead deferring the [20:13] loading of the object and deferring the constructor of the object to the latest constructor of the object to the latest possible point where you need the values uh that are assigned to the properties there. Thank you very much for [20:26] listening. Uh I hope to see you back soon.