Speaker 1: Okay, let's unpack this. Imagine writing like ten thousand lines
Speaker 1: of pristine, beautifully architected, object oriented PHP code.
Speaker 2: Oh that's the dream, right right.
Speaker 1: You have your classes, your properties, your inheritance trees, all
Speaker 1: your objects are just neatly interacting with one another.
Speaker 2: Yeah, a perfect domain model exactly.
Speaker 1: And then after all that elegant design, you have to just,
Speaker 1: you know, smash that entire architecture into a flat, rigid,
Speaker 1: two dimensional grid of database tables.
Speaker 2: It really is the ultimate architectural heartbreak. I mean it's
Speaker 2: a huge problem.
Speaker 1: It totally is because your PHP application it speaks this
Speaker 1: rich language of objects, but your database it only understands tables, columns, rows,
Speaker 1: and foreign keys.
Speaker 2: Right. They are two completely different paradigms, and.
Speaker 1: Bridging that gap manually usually means writing just endless, repetitive
Speaker 1: seqle queries just to save affect your data. It completely
Speaker 1: bogs down your whole workflow, it does.
Speaker 2: I mean, that fundamental disconnect is honestly one of the
Speaker 2: biggest hurdles in opp location development. Oh. Absolutely, You spend
Speaker 2: all this energy building a domain model that represents your
Speaker 2: business logic, only to have to write dozens of mapping.
Speaker 1: Functions have to tear those objects.
Speaker 2: Apart, exactly, you tear them apart just to fit them
Speaker 2: into a relational database structure. It completely disrupts your focus.
Speaker 1: You stop thinking about how your app should behave and
Speaker 1: you start agonizing over like how to keep your memory
Speaker 1: state synchronized with a hard drive.
Speaker 2: Which is exactly what we want to avoid.
Speaker 1: So for today's deep dive, we're looking at the solution
Speaker 1: to that disconnect. We are talking about the doctrine ORM
Speaker 1: or object relational mapper, a.
Speaker 2: Life saver for PHP developers, truly.
Speaker 1: We're going to decode how doctrine acts as the ultimate
Speaker 1: highly skilled UN translator, working flawlessly between those two entirely
Speaker 1: different languages.
Speaker 2: Like that analogy a UN translator, right, And we'll.
Speaker 1: Use a standard blog engine as our guiding example today
Speaker 1: to see how it takes all that heavy lifting off
Speaker 1: your plate.
Speaker 2: Sounds perfect.
Speaker 1: So to understand how doc doctrine pulls this off, we
Speaker 1: have to start with the brain of the operation, which
Speaker 1: is the entity manager.
Speaker 2: Yeah, the core of doctrine.
Speaker 1: Now, in doctrine, the actual data objects like your blog
Speaker 1: posts or user profiles. Those are called entities. But these entities,
Speaker 1: they're just plain old PHP objects.
Speaker 2: Right, They don't extend some massive database library or anything.
Speaker 1: No, they have absolutely no idea that a database even exists.
Speaker 2: And you know that is very much by design. It
Speaker 2: relies on a specific architectural pattern called the data mapper.
Speaker 1: Okay, data mapper, How is that different from what other
Speaker 1: frameworks do?
Speaker 2: Well? In some other frameworks, you might see the active.
Speaker 1: Record pattern, oh right, where the object itself has a save.
Speaker 2: Mefit exactly, it talks directly to the database, but doctrine
Speaker 2: deliberately separates those concerns.
Speaker 1: So the entity's only job is to just hold your data.
Speaker 2: Yes, and the entity manager is a completely separate service
Speaker 2: that handles the really complex job of mapping that data
Speaker 2: and saving it.
Speaker 1: Using something called the unit of work.
Speaker 2: Right, Yes, the unit of work. It's a crucial concept here.
Speaker 1: The unit of work concept is honestly brilliant. I like
Speaker 1: to think of the entity manager as a highly experienced
Speaker 1: waiter taking your order at a really busy restaurant.
Speaker 2: Okay, walk me through that.
Speaker 1: Well, the application you're making changes. You tell the waiter
Speaker 1: you want a steak, then you change your mind to chicken.
Speaker 1: Then you add a side of fries that maybe cancel
Speaker 1: a drink order. That waiter doesn't sprint back to the kitchen,
Speaker 1: which is our database in this case, every single time
Speaker 1: you open your mouth.
Speaker 2: Now, if the waiter did that, the kitchen would be
Speaker 2: completely overwhelmed exactly, which is exactly what happens when an
Speaker 2: application fires off a new sequel query for every single
Speaker 2: tiny variable chain.
Speaker 1: It's chaos. So instead, the waiter stays at your table
Speaker 1: writing everything down on their night pad.
Speaker 2: And that notepad is the unit of work spot on.
Speaker 1: It tracks all the modifications, the new additions, the items
Speaker 1: you want removed. And it is only when you are
Speaker 1: completely finished and the waiter walks over to the kitchen
Speaker 1: enhance in that final consolidated ticket that the database actually
Speaker 1: gets to work.
Speaker 2: And in doctrine, handing in that ticket is done by
Speaker 2: calling a specific method called flush flush. And what's fascinating
Speaker 2: here is the optimization that happens when you call that
Speaker 2: flush method. It's really the core power of doctrine.
Speaker 1: You mean an example of that optimization.
Speaker 2: Well, let's say you retrieve a blog post entity from
Speaker 2: the database. In your code, you execute a change like
Speaker 2: you set my entity name to my name. Then maybe
Speaker 2: immediately after a different function changes that name again to
Speaker 2: let's say.
Speaker 1: Kevin, so change twice in memory.
Speaker 2: Right, Doctrine's unit of work is tracking both of those
Speaker 2: changes on its notepad. But when you finally call flush,
Speaker 2: Doctrine doesn't waste time running two separate update queries.
Speaker 1: Wait, really, it just skips the first one exactly.
Speaker 2: It compares the original state of the object to its
Speaker 2: final state, realizes only the final name matters, and issues
Speaker 2: a single sequel query. Oh wow, yeah, just one update
Speaker 2: my entity set name equals kevin where it equals thirteen
Speaker 2: to twelve using prepared statements.
Speaker 1: That is incredibly efficient.
Speaker 2: It is, and it handles the other entity manager states
Speaker 2: just as seamlessly, like find to retrieve an object, or
Speaker 2: if you create a brand new entity, you use persist.
Speaker 1: Which basically introduces it to the entity manager. Right tilling
Speaker 1: Doctrine like, hey, watch this new.
Speaker 2: Object exactly, and if you want to delete something, you
Speaker 2: call removed.
Speaker 1: So you just schedule it for deletion. Doctrine notes it
Speaker 1: on the notepad and waits for the flesh.
Speaker 2: Yes, and by sinking everything in one massive batch during
Speaker 2: that flesh call, Doctrine can wrap all those changes in
Speaker 2: a single database transaction.
Speaker 1: Okay, why is that transaction part so important?
Speaker 2: Because if anything goes wrong during the flesh, say you're
Speaker 2: saving a new user, generating their profile, and creating a
Speaker 2: welcome post, all at once, a lot of inserts, right,
Speaker 2: and if the very last insertion fails because of a
Speaker 2: network kickup, the database automatically rolls back to its exact
Speaker 2: previous state.
Speaker 1: Oh that's huge.
Speaker 2: Yeah, keeping data perfectly consistent, you are completely protected from
Speaker 2: ending up with half saved.
Speaker 1: Data, which could corrupt your entire system. Okay, so the
Speaker 1: entity manager is our waiter, perfectly batching our changes. But
Speaker 1: it can only do that if it has a map.
Speaker 1: It has to know that the title property in our
Speaker 1: PHP object corresponds to a specific text column in our
Speaker 1: SQL database. Naturally, but we just established that our entities
Speaker 1: are plain PHP objects with no database logic. So where
Speaker 1: does this map actually live?
Speaker 2: So doctrine uses doc block annotations to build this map
Speaker 2: annotations right, These are specially formatted comments placed directly inside
Speaker 2: your PHP code, right above your classes and properties.
Speaker 1: Wait, like literally in the code comments exactly.
Speaker 2: You use specific tags like at entity or at table
Speaker 2: above your class to declare it as a database table,
Speaker 2: and tags like at column at id or at generated
Speaker 2: value above your properties to define exactly how they map
Speaker 2: to the columns.
Speaker 1: Here's where it gets really interesting, though, because putting database
Speaker 1: configurations right inside PHP comments doesn't that clutter up the code?
Speaker 1: I mean, because my entity is suddenly deeply aware of
Speaker 1: my database schema. We're usually taught to keep those things
Speaker 1: strictly separated, right.
Speaker 2: Yeah, I mean that's a very common point of friction
Speaker 2: for developers transitioning to doctrine. I can imagine the purest
Speaker 2: view is that your domain objects should be completely oblivious
Speaker 2: to how they're stored, maybe using separate XML or Yammel
Speaker 2: configuration files right. But practically speaking, keeping the mapping information
Speaker 2: right next to the code vastly improves readability and maintenance.
Speaker 1: Because it's right there in front of you.
Speaker 2: Exactly when you look at a property, you instantly know
Speaker 2: how it behaves in the database. You don't have to
Speaker 2: hunt through massive external files to see if a string
Speaker 2: can be null.
Speaker 1: I guess I could see the practical trade off there,
Speaker 1: And the way doctrine handles mapping types is pretty cool too. Oh.
Speaker 2: The type casting is a massive timesaver.
Speaker 1: Because doctrine types aren't purely PHP, and they are purely SQL.
Speaker 2: Right right. They act as a transparent bridge. For instance,
Speaker 2: Doctrine's text type becomes a standard string in your PHP.
Speaker 1: App, but in the SQL database.
Speaker 2: It's automatically stored as a c lobrie, a character large
Speaker 2: object suitable for massive blocks of texts.
Speaker 1: That's so seamless it is.
Speaker 2: But we do need to point out a really critical
Speaker 2: limitation here. While Doctrine handles the mapping, it does not
Speaker 2: do data validation. If your annotation says a column has
Speaker 2: a max length of fifty characters, Doctrine builds the database
Speaker 2: column to that exact spec. But if a user submits
Speaker 2: one hundred characters, Doctrine won't stop them. It won't stop them,
Speaker 2: it will try to save it, and the database will
Speaker 2: throw a fatal error. You still have to validate user
Speaker 2: inputs separately, So it's.
Speaker 1: A mapp or not a bouncer. Yeah, good to know.
Speaker 1: But honestly, the absolute best part of defining all those
Speaker 1: annotations is the command line magic.
Speaker 2: Oh, the schema tool.
Speaker 1: Yeah, by running I think it's a an orm colon
Speaker 1: schema tool Colon create, that's the one. Doctrine reads all
Speaker 1: those annotations and automatically generates the entire underlying database schema.
Speaker 2: Cables, columns, primary.
Speaker 1: Keys, without the developer writing a single line of sequel.
Speaker 1: It completely removes the busy work.
Speaker 2: It's incredible for bootstrapping a new project.
Speaker 1: Okay, so mapping a single blog post is straightforward, but
Speaker 1: things get complicated fast when we add comments and tags right.
Speaker 2: Enterprise apps aren't just isolated tables exactly.
Speaker 1: Relational databases use foreign keys and joining tables for this.
Speaker 1: How does doctrine handle these complex webs well.
Speaker 2: It manages them through association types, also defined using annotations
Speaker 2: like at onetominy for a post to comments relationship.
Speaker 1: Or at minutomany for posts to tags exactly.
Speaker 2: But to make these relationships work, you have to understand
Speaker 2: a concept that trips up almost everyone.
Speaker 1: At first, let me guess the owning side versus the
Speaker 1: inverse side.
Speaker 2: Yes, using the inverse buy and mapped by attributes, it
Speaker 2: is a notorious stumbling block.
Speaker 1: It totally is. I like to use a dog leash
Speaker 1: analogy for this one.
Speaker 2: Okay, let's hear it.
Speaker 1: So the owning side is like holding the leash of
Speaker 1: a dog. The dog, which is the inverse side, might
Speaker 1: know it belongs to you. But if you don't actually
Speaker 1: grab the leash, meaning setting the owning side and the
Speaker 1: codoctrine won't save the connection.
Speaker 2: That's a great way to put it because the crucial
Speaker 2: rule is that doctrine only manages the owning side of
Speaker 2: an association. So if you have a method like ad
Speaker 2: comment on your post entity, you can't just push the
Speaker 2: comment into an array. That method must include a line
Speaker 2: like comment arrow set post.
Speaker 1: You have to explicitly grab the leash exactly.
Speaker 2: If you don't, the relationship evaporates when you flush.
Speaker 1: And speaking of arrays, doctrine doesn't actually use standard PHP
Speaker 1: arrays for these relationships right now.
Speaker 2: It uses a special class called array collection.
Speaker 1: Why is that If it's just a list of comments,
Speaker 1: why not just use a normal array?
Speaker 2: Because a normal array is completely passive. Doctrine needs to
Speaker 2: track the internal state of that collection.
Speaker 1: Oh, to know what to sync with the database.
Speaker 2: Right it acts like a standard PHP array, but provides
Speaker 2: the hidden hooks. Doctrine needs to figure out what to
Speaker 2: update or delete.
Speaker 1: And that enables some really cool features like orphan removal equals.
Speaker 2: True yes, automatically deleting tags that are no longer linked
Speaker 2: to any post.
Speaker 1: Or cascade equals persist.
Speaker 2: Or saving a post automatically saves all the brand new
Speaker 2: comments attached to it.
Speaker 1: But if we connect this to the bigger picture, all
Speaker 1: these webs of objects must impact performance.
Speaker 2: Right massively, especially regarding how the data is loaded. By default,
Speaker 2: Doctrine uses lazy loading, meaning.
Speaker 1: It only fetches the relations when you explicitly ask for them.
Speaker 2: Right, If you ask for a post, it leaves the
Speaker 2: comments behind. If your code asks for them later, it
Speaker 2: quietly fires off a second query to fetch them, which.
Speaker 1: Saves memory initially. But what if I know I need
Speaker 1: all the comments? Isn't that second query wasteful?
Speaker 2: It is? That's the classic N plus one query problem.
Speaker 2: To fix it, you switch to eager loading using fetch
Speaker 2: equals eager in your annotations, so.
Speaker 1: It fishes them immediately in one big joint exactly. Okay,
Speaker 1: so we successfully save these interconnected webs of objects, but
Speaker 1: how do we get them back out efficiently without resorting
Speaker 1: to raw SQL.
Speaker 2: We need a new way to ask for our data,
Speaker 2: and that is Doctrine query language or BIKIL. Yes, and
Speaker 2: the massive paradigm shift here is that DQL queries the
Speaker 2: object models entities, not the database tables.
Speaker 1: Right, So you're not writing select star from posts left
Speaker 1: joint comments on.
Speaker 2: Now you write something like select pc from blog slash
Speaker 2: entity slash post p lft joinp dot comments c.
Speaker 1: That is wild. You just use the properties.
Speaker 2: It's extremely powerful. And to keep these organized, Doctrine uses
Speaker 2: entity repositories, which.
Speaker 1: Is the table data gateway pattern exactly.
Speaker 2: The base repositories give you magic methods like fine by
Speaker 2: title where you don't even write query logic.
Speaker 1: But for complex stuff we use custom repositories and the
Speaker 1: career builder.
Speaker 2: Right. Yes, you build a query by calling methods like create,
Speaker 2: query builder left, join ad select. It translates seamlessly into SQL.
Speaker 1: So what does this all mean for security? Because I'm
Speaker 1: thinking about SQL injection? Like if someone types quote r
Speaker 1: quote a quote equals quote.
Speaker 2: A ah, the classic exploit.
Speaker 1: Right, does the query builders stop that?
Speaker 2: Absolutely? The query builders set parameter method acts as a
Speaker 2: built in security guard. It automatically escapes inputs some malicious
Speaker 2: code can't sneak through.
Speaker 1: Oh that's such relief.
Speaker 2: And to boost performance, Doctrine caches these generated DQL queries.
Speaker 2: It parses at once, cashes the sequel, and just swaps
Speaker 2: the parameters next time.
Speaker 1: Wow.
Speaker 2: You can even use aggregate functions like count dc dot
Speaker 2: I to return custom two dimensional arrays, like getting a
Speaker 2: post with its total comment count.
Speaker 1: Okay, so we've mathtered standard relations, but let's push the limits.
Speaker 1: What happens when our data model hits a classic OOP
Speaker 1: concept that relational databases despise.
Speaker 2: You mean inheritance inheritance? Yea, relational databases really hate inheritance, right?
Speaker 1: So if I have a post author and a common
Speaker 1: author that both extend an abstract auter class, how does
Speaker 1: doctrine handle that?
Speaker 2: It offers three inheritance strategies. The first is mapped superclasses.
Speaker 1: Okay, what's that?
Speaker 2: It's good for sharing properties in your PHP code, but
Speaker 2: at the database level, the child classes get completely separate.
Speaker 1: Tables, so no real relation in the dB. What's the second?
Speaker 2: Single table inheritance. It puts everyone in one massive table,
Speaker 2: using a discriminator column usually called D type to tell
Speaker 2: them apart.
Speaker 1: I love my closet analogy for this one, but you're
Speaker 1: the closets. So single table inheritance is like throwing everyone's
Speaker 1: clothes into one giant closet, but slapping a sticky note
Speaker 1: like the D type on them.
Speaker 2: Highly performant, but a bit messy because.
Speaker 1: You have a lot of empty space for properties that
Speaker 1: don't apply to everyone exactly. But the third strategy class
Speaker 1: table inheritance. Yeah, that's like giving everyone their own separate closet. Right.
Speaker 2: Each class gets its own table.
Speaker 1: But if you want to put together an outfit, you
Speaker 1: have to open all the closets, which.
Speaker 2: Which means your queries require massive joy and ends. It's flexible,
Speaker 2: but much slower.
Speaker 1: This raises an important question, which one should I actually use?
Speaker 2: Well, it's about architectural trade offs. Single table is usually
Speaker 2: best for performance. Class table is really reserved for highly
Speaker 2: complex models where performance isn't the primary bottleneck.
Speaker 1: Makes sense now real quick? Before we wrap, we have
Speaker 1: to mention doctrine's event.
Speaker 2: System AH life cycle events right.
Speaker 1: Like using at hass life cycle callbacks and at pre
Speaker 1: persist to automatically stamp a publication date right before insertion.
Speaker 2: It's very handy, but be careful. More complex logic like
Speaker 2: an insult event listener that scans for bad words or
Speaker 2: emailing an author, look at that in the entity exactly,
Speaker 2: Keep entities clean. That logic belongs in external event subscribers.
Speaker 1: Good rule of thumb. Wow, Okay, we've journeyed from the
Speaker 1: raw connection of PHP to the elegant abstraction of doctrine.
Speaker 2: Today we covered a lot of ground we did.
Speaker 1: We saw how the Entity Manager Association's DQL and advanced
Speaker 1: inheritance make handling data an object oriented dream.
Speaker 2: And for you listening, this means faster development, cleaner code bases,
Speaker 2: and letting the framework handle the heavy lifting.
Speaker 1: Yeah, database, synchronization, security, you name it. You can just
Speaker 1: focus on building features rather than writing boilerplate sequel.
Speaker 2: But consider this, doctrine does provide native queries and DBL
Speaker 2: access for when you need raw database specific power, right, which.
Speaker 1: Makes me think, As ORMs become more magical and do
Speaker 1: more of the thinking for us, do we risk losing
Speaker 1: our fundamental understanding of what makes it database actually performant
Speaker 1: under the hood.
Speaker 2: It's a valid concern.
Speaker 1: Does the ultimate abstraction eventually abstract away our core engineering skills?
Speaker 1: Something for you to ponder before your next deep dive.