 pretty good audience there. So, my name is Larry Garfield. I work for Palantir.net. We're a consulting firm based in Chicago. We do mostly but not exclusively Drupal work. We've also done some Sylex work and some symphony work. We work mostly but not exclusively with institutional nonprofits. Universities, museums, public radio stations, hospitals, some media companies as well. It's kind of our standard. We're one of the sponsors. For more information, we do stop by our booth. I'm going to be there in and out. So, enough commercial. So, Sylex. What is Sylex? Sylex is a micro framework built on these symphony two components. What does that mean? It means it's an unopinionated symphony. It's the best way I've heard it described. It's, you know, with symphony full stack, you get a fully built full stack framework with a lot of functionality baked in, ready to go, templating system, database system, and so forth. Not as much as you get baked in with Drupal Core but a lot of functionality there ready to go. And most importantly, an architecture implied. If you do, there is a symphony way of doing things, TM. Just like there's a Drupal way of doing things. There's not really one way of doing things in Sylex. That's by design. Sylex is a micro framework, micro in the sense of it does not imply a given architectural style. This gives you the freedom to implement the architectural style that you want. It also gives you the responsibility to implement the architectural style you want. And to do it well. As I said, there's no one right way to do Sylex. Different people have a different spin on it. What we're going to go through here is essentially how to do Sylex well Larry style. So this is the Krill endorsed way of using Sylex. It's not the only way, it's just the way that I find helps to keep a Sylex application maintainable. So we're going to go through a fairly basic Sylex application starting from scratch. If you want to follow along, there's the source code, github.com slash pal internet all in word, zero dash two dash Sylex. I'll also be tweeting a link later if you don't want to download it now. So let's just go ahead and dive in. So let's start as any good PHP app does with a composer file. And we pull in Sylex. The entire system is a third party dependency of your code. Unlike, say, with symphony, which, you know, fusing symphony standard edition comes with a lot of stock code you're supposed to use. Every single line of code we have here is something I have written. Not stock code that comes with some standard Sylex toolkit. There are standard Sylex toolkits. I don't use them. I prefer to just roll my own. This is my standard Sylex toolkit. So we include Sylex itself. And that gives us various symphony components that it's built on. So use the same HTTP foundation, HTTP kernel that symphony uses and Drupal 8 and Laravel and a bunch of others. Same routing system. So a lot of us will look vaguely familiar, at least conceptually. As far as our own code, let's go into our web doc group. And that's the entire application. We create a new instance of this Sylex application object and run it. And we're done. We now have a working application. And if we load that up in a browser, we get an error message. This is great, actually. This is a very good place to start because we're getting a proper formatted 404 message, which means routing happens. The routing system is already there and working for us. And error handling is there, so you don't have to think about 403s and 404s and so forth. That's already taken care of. Great. We now have a working application. Let's go home. Okay, now let's add features to it. So let's start some basic routing. After we create this application, we can add routes to it. To add a route, there's a lot of methods you can just call directly on the application. In this case, we're going to create a get route, HTTP get, for the slash about page, which is going to respond with that controller. This is a controller just like anything else in symphony. In this case, we're just specifying it as an inline anonymous function. It can be any callable, callable meaning thing in PHP you can print these after. Function name, object and method, class and method, anonymous function. I think that's it. So we're just going to make it inline and return a new instance of symphony's response with some text message. And that response, actually, Drupal people. Quick question. Who was at my Drupal 8 talk earlier? Most of you. Okay. This should look very familiar to that presentation, actually, because these are built on the same foundation. We're just returning a response object. And that's the entirety of what we're adding. If we want to respond to a put request, then we do app put instead. If we want to respond to a post, it's app post controllers for each HTTP method. If you want to respond to all methods, which honestly I almost never do, then you just say app match. Go to slash about. There's our response. This is a super simple messaging service. Questions at that point? I haven't seen it. So let's do something slight bit more interesting. So as in any good application, your business logic should not be in controllers. Your business logic should be in services. So let's add a service. Object is actually subclassing from Pimple. Pimple is the world's simplest dependency injection container that people actually use in practice. It's about 100 lines long, maybe 150 lines long, and works with PHP's array access methods and anonymous functions. It's actually really, really cool, and it's what everything in Sylex is built off of. So we're going to create a parameter, just like in the symphony container, you get parameters, same idea here, called rot and code. We're going to register a new service called rot and code. That service is, this is the anonymous function, or the callable, can be any callable, that will instantiate this service when called. And we wrap it in an app share call, which means cache this and only convention it once. So right there, we've got everything we need to create this service. We just close over the app itself, and then return a new instance of this rot and code class, and pass as its first parameter that rot and code count. What does this rot and code class? It's a very simple encoding system that uses rot and coding. Who's used rot and coding before? It's a very basic form of encryption, potentially two way encryption, where you just take every character and rotate it. So rot one is A replaced with B, B replaced with C, C replaced with D. Rot two is A replaced with C, B replaced with D, and so forth. Which means rot 13 is reversible. Rot 13 is something to encode it. Rot 13 is something to get back to the original. This is also known as the absolute worst security system in the universe. It serves no purpose other than to be used in slide demos. So this is just a simple class that has a, it takes one constructor parameter, and it takes a string, the php.net documentation as an example. Please never use this in production, okay? Promise me that. We've registered that service. That service is completely standalone. Nothing to do with Silux itself, which means easy to unit test. That's what you want. We're going to modify our controller to rot and code that string instead. And we're just going to return the string rather than a response. What? One of the nice things with Silux, it automatically has a view listener registered. If you return a string from your controller, it will automatically get just wrapped up into a response. If you want to return something that's not a string or a response object, you write your own view listener. We'll see that in a little bit. Rot encoded, or rot 13 encoded version of this string. Rot 26, what does that actually do? Gets back your original string, which is technically still encryption under the DMCA, so reading something is a violation of the DMCA. I always have to throw that one in there. So this is great for going to just hard code everything. Usually you don't want to do that. So let's add some services from someone else. In Silux, if you want to package up a service, you can write something called a provider. A provider is a class that says, hey, Silux, here's a bunch of services. Enjoy them. And Silux ships with a couple of providers, not the libraries for them. You have to download those separately. In this case, we're going to call app register that service provider that comes with Silux. It's just a new instance of doctrine service provider, and the parameters that we give that service provider. In this case, we're going to say we're creating a database with SQ Lite. Here's the path to it. Great. In a controller or wherever else, we can access the database connection just out of the container. Nope, this does mean your controllers here are container aware, which is a bad thing, and have whatever logic you're doing. Incidentally, we have this registered as a get method that's going to drop and recreate the database. Please do not actually do this in production. It's just a convenient place for me to show that, hey, yes, you can call doctrine. So far, we're up to about 60 lines of code in our index PHP file. Who thinks this is going to scale well? Didn't think so. Generally speaking, putting a couple thousand lines of code into one giant index PHP file is not a good idea. So what's the alternative? There's a couple of different ways that people solve this. Just extend that application class yourself. Got an application that extends the basic Silux application instructor, which is going to move all of the code you just had into here. Nothing really exciting here, just moving code around. And I like to break it up into a couple of utility methods. You don't have to, but I find it convenient. So we've got our custom services, third party providers, any routes we're going to create. No new code here. I just moved it into a class. And then our index PHP is just create our new application. It's a subclass of Silux and run it. And we can also flag debug mode true if we're going to leverage that. That's not a magic variable. It's just the name that I decided to give this property. One issue here. A huge problem when I'm using SQLite. I'm using MySQL or Mongo or Postgres. Who thinks it's a good idea to hard code your database credentials into your application class? Good. So how do we provide configuration? Silux, by default, doesn't have a configuration system in it. It provides nothing out of the box. So let's go get one. The standard one that is generally used is from Igor Weider. I think this is how his last name is pronounced. It's a third party download. Just pull it in with Composer. And then we register that service provider. A directory name. And it can handle configuration files in JSON, YAML, PHP, and I think one or two other formats. I like to use JSON because these are going to be parsed on every page request. There's no automatic caching and container building going on here. So this parses in every page request. And JSON is a lot faster to parse at runtime on every single page request than YAML is. If you want to use YAML though, go ahead. And I'm actually going to load it twice. Let's see why in a moment. Different parameters. In this case, I'm going to specify a different file name. Let's have a look at these config files. So that settings is just, you know, an adjacent file that has a path to our database, or a name of our database. We can also put the odd count in here. In this case, we're setting it to 26, which means transparent, but still actually encryption, and therefore illegal to read. And we can specify an environment. This is not a required way to set things up, but this mirrors the way that the typical symphony app is set up. So your settings JSON file, you don't check in to get, because this has your database credentials. This has, or even dev mode or production mode. Sylex doesn't understand the difference. I'm building the difference in myself right here. Because that environment, everything here becomes available as a property on that dollar app variable. Now that first line there loads up that settings file. We now load up whatever our environment is. So we're in dev JSON, which turns on debug. Load of prod JSON. All of these will just be merged into that gigantic app object. Don't want to set it up this way. Don't set it up this way. This I find is very convenient to have everything checked in to get, that I need checked in to get, and still support different operating environments. And if you want to have multiple config files for different types of configuration, you absolutely can. Just call that provider multiple times again. Okay. So we've got the providers, but we're still registering this service of ours ourselves, this rotten code service. Wouldn't it be cool if we could write our own providers instead? Anybody? Yes? Okay, thank you. Fortunately, we can. It's actually quite easy to do. I'm going to implement the service provider interface. The Sylex interface, Sylex provided interface, which has two methods on it. Register and boot. Nice and simple. Register gets past the application, and you call the exact same stuff on it that we just had in our application class a moment ago. Boot does exactly the same thing, but gets called after all of the registers. This lets you vary what gets registered based on some other service provider. So it's really just a two-pass approach. But exact same code. And then in the application, we just register our rotten service provider. Now note the important thing here is the order, because all of these providers will get called in the order you specified them in the code. And if they conflict, whichever gets called last wins. That's how you can have overrides for, say, a different environment. It also means rotten service provider can, by default, have an encoding of 13. But configuration gets loaded later, so if it specifies that rotten coding count, that wins over what the module provides by default, or what the provider provides by default. Which means just by keeping the order straight of our providers, we can have default configuration for some kind of bundle and overrides, just by varying the order. It's a very, very simple approach. Okay. What else could be cleaned up here? All of these routes. Can we move those out of the app class? Well, yes, we can. You also have controller providers in a very similar fashion. It just has any arbitrary class, implements an interface, and has a connect method on it. But you get to pass the application. And then I'm not entirely sure why, but you need to get access to the controller's service, which is really just the symphony routing component, and get on that directly. Why the documentation always says to do this, and why you then need to return the controller's service itself, I have absolutely no idea, to be perfectly honest. But this is what works. So again, all I'm doing here is moving code out of base application class into this provider. And I can then put these provider classes anywhere I want. There's no rule about it. So if I want to wrap up this route encoding service as its own standalone PHP package, and then have a little bridge package for Sylex, that has the provider, then I just make those two separate composer packages, require those in my composer JSON file. In my code, then I just, you know, register that provider. And poof. I've now got a completely freestanding PHP library wired in to Sylex. And because there's nothing really going on here, there's no magic happening. It's very lightweight. It's just loading classes. That's really all that's going on. Let's try and do something actually interesting because saying hello is not the greatest application. So what should we do for a demo? I should point out that at least in the United States, federal law requires that if you're demoing a framework, your sample application is a blog. And if you're demoing a REST API, your sample application is Twitter. And I have no interest in annoying the feds. So we're going to implement Twitter because what else are you going to do? So what are we going to need for a Twitter application? Well, we're going to need users and messages. So I like to break out my application into kind of... Modules is almost a too heavy a term for the Drupal people. But essentially, I'm going to create modules here really just by putting them in separate namespaces. So I'm going to create a users directory namespace and create a user repository, which is just a wrapper around you're doing a find by username on doctrine, crud stuff. Users have a username and an age. And note here, we get the doctrineDBAL connection object as a dependency, which is the only dependency we have is on doctrineDBAL because it's a repository. To register that, service provider, where we're just returning a new instance of the user repository with its first parameter being that doctrine object. This gets you the same results as wiring things up in the symphony dependency injection container, but it's one anonymous function callback rather than compiler passes and YAML files or maybe XML files or whatever else it is you're going to do. This is about as simple as you can possibly get. You got a question? So the appshare method. This is part of Pimple. So let's actually just look at this example directly. So we've got fdb is a service registered this exact same way by the doctrine service provider. When we call appdb, that uses php's underscore underscore to be php's array access methods, and it says, do I have an instantiated appdb yet? No, I haven't. All right, I've gotten an anonymous function registered for that, I will call that, and whatever comes back from it, be that an object, be that a string, be that the resource, be that whatever you feel like returning is what I will then return. Wrapping an appshare means the second time it gets called, oh, I already did that, I'll just return the same one as last time. It's actually a little bit weird that you have to call appshare around that because honestly that's the typical use case. So actually Pimple 2, this is a Solix 1.2 which is using Pimple 1. Pimple 2 reverses that. So the default is to have a shared service, and there's another thing you wrap it in to make it not shared. That answer your question? So it's just like in any other dependency ingestion container, this gets called the first time someone needs that service, and if that means it needs to instantiate some other service lazily at that point in time, it will do so. And then we're also going to set up some controllers. So we're going to have posts to slash users, take the request object, get its content, JSON decoded, this is a JSON based API, take that data, create a new user, and then redirect to the user we just created which is what you're supposed to do in HTTP when you create a new object. As you send a 201 created response with the URL that you should redirect to. So we use symphony's URL generator, which is the same thing you're used to from symphony and it's also in Drupal 8 to generate a link to the users.view route with this as the parameter, the username that we just created, and then we call apri direct. But what's this users.view route? We haven't created that yet. That's down here. So in this case, this is a get request bracket user, which is this bind by username on it. Just look up the user, strip off the internal ID because you shouldn't expose that in a REST API. And then return that as a JSON blob. This is just the utility wrapper around new JSON response and data. And then we're going to bind it to users.view. Every route in any symphony needs to have a name. It needs to have a machine name. In Sylex, if you do not specify one, it creates one magically for you behind the scenes, but you don't know what it is, so it's not useful to you. If you want to refer to a route, you have to give it a name by binding. But once you do that, then we can create a link to it and redirect to it. Remember I said nothing comes by default? Yeah. So we're going to add that line and you're done. And then we're also going to register our user service provider, which lets us register those controller providers. The first parameter is everything provided there should have this prefix on it. In this case, we're saying both things just come from the app route. You could also set it up so that everything is slash users and then whatever. But if it's not the route provider and everything is southbound, it's the controller provider. Okay, so I'll just go to a couple of things. I'll firstken out that very useful people we do not allow that for Drupal8. One problem here we said before, that app class looks like it's getting... or the index php looks like it's getting really big and full of anonymous functions. And anonymous functions are really hard to test. Those route providers look like they're getting really big, as services in symphony and in Sylex and in Drupal if you are so inclined, although we usually don't. So, to add another, once again, we have to register another provider that gives that ability to the routing system. For a provider, I'm going to, instead of specifying an inline callable, I'm going to give it the name of a service and a method. This is the exact same syntax you can use in symphony for service and method that will be a controller. And same thing here. Our service provider, our user controller class, which is our service, users.controller, that's the name we gave it before. In instantiated, we call that method on it, on that class. We're just taking the same code and moving it into actual methods. Which is where real code belongs? One thing to know, I am using, I am actually passing the application object into the constructor here, which means I do have container aware services. Or excuse me, container aware controllers. Is this a good thing? No one can agree on that. The first Sylex app I did, I did not do that. I actually passed in services directly and that got very clumsy. These days I go with the rule of, if this becomes a problem, it's because you have way too much logic and your controllers move it out. So, again, you know, pick your poison on that one. You can go either way. For our purposes, just to keep things simple, I'm going to pass in the container to the controller, but nowhere else. This is the exact same code we had before. Except, getUser is not getting passed a user ID. It's getting the full user object that we loaded from the database. How does that happen? Because we had this convert callback. This one, I believe, does have to be an inline callback. But this lets us upcast those parameters. So we have a user placeholder here. So the user, if provided, will get passed to this callable and replaced with whatever it returns. So in this case, we're going to look up that user by name from the repository and return that loaded user. And if that breaks, the repository will already throw an exception for us and that'll get handled as a 404 elsewhere. Which means our controller now does less work. It doesn't need to load the object. All it's doing is formatting it. This is a good thing. Cool. Also, user objects. Again, we're going to bind to it. Great. Here's our putUser method. Again, JSON decode gets put. Update the object. Save it. And then HTTP is actually unclear about what you're supposed to do after a put request. What I'm going to do is respond the exact same way I would had it been a get request. So we've saved the object. We're now going to return the same thing you would get if you just hit it again with a get request. Is that what you have to do? No. That's what I find convenient to do. We've got users. Great. We've got a system where we can create and update users through the system. We could also delete them. That'd be easy to add. So now, let's check out messages. Messages we'll put in our own separate module. Again, scare quotes module here. Application. We register its services which looks pretty much the same as the user repository. You could simplify this if you want to. The message itself, an author, and a parent because you can respond to messages. It's just some internal implementation details, whatever. Here's our service provider. Looks pretty much the same. The provider looks very similar. With one distinction, we also have a post. So messages live at slash messages, which is a list of all messages slash message ID, or messages you can reply to them. So if you send a post request to messages slash message ID reply, do the same upcasting here as we're doing for a get, okay? Create message. Works pretty much exactly the same as it did on users. Send a post request to slash messages. Create a new message. We send back a redirect. Get a message. Fine. Sure. Nothing different here except, oh, the message author. We're going to overwrite with the author's name rather than internal ID. Again, good rest of the API practice. You don't expose your internal IDs. It's going to also create a new message, but it will populate the parent field right here based on the message that's passed in. So notice here we've got the message being replied to, the parent message, and the request for the message. We send a redirect to the newly created message just as we would. Questions at this point? We don't have a working Twitter app, and it didn't take very long either. Can we go further than this? Absolutely. Errors happen. Because, well, we're humans, and humans make mistakes, at least some of us do. I don't know about you, but I'm not perfect. I make mistakes, and certainly my users make mistakes. In symphony, most HTTP errors are handled via exceptions. The routing system or the access control system or whatever will throw an exception that gets caught by an exception listener and convert it into a response. That's how symphony works. That's how Sylex works. That's how Drupal 8 works. Let's register some error listeners. App error is a shortcut for accessing the exception event in the symphony kernel. I say those words. Does everyone understand what I mean when I say the exception event of the symphony kernel? Does anyone not know what that means? Okay, good, on the same page. So each of these is just a callable that will be the body of the listener for that event. But what's really cool is you can type hints on the type of exception that gets thrown, which means we can just specify, hey, if we get a not found exception, a not found HTTP exception, this is what gets called. I have an object not found, which is what gets thrown by our repositories. This will get called. This listener has a lower priority than those, so it gets called at the end, and it will catch any exception. What do we do with it? Well, again, Sylex doesn't tell you. It just says, hey, as long as you get a response back, I'm cool. So what I'd like to use here is a library called API problem. API problem is one of two REST error specifications out right now. The other is VND error. Personally, I like API problem better, and so I wrote a library for it. It's just a standard library on packages if you want to download it. Very simple, one class, plus tests, of course, for formatting a error message in a standard JSON form. Because you don't want to return an HTML error if someone sends you a JSON request that's invalid, right? You want to send back something that is vaguely what it expects. So in each of these cases, we just use this API problem library to specify, all right, here's the error information, the body of the exception is going to be our detail field, and then get back that information as an array, create a new JSON response with that, send a 404 not found in this case, calls it based on priority in Sylex, or in Symfony, higher number wins. It's a priority, not a wait. So what it'll do here is, exception gets thrown, first thing it checks is, is it an object not found? Is the exception an object not found in exception? Nope, all right, check the next one, which is this one, all right, is the type of it not found HTTP exception? Yes, okay, call that, we're done. This does register three different listeners. So it is three different listeners in the event spatter. It actually, what it does is take each of these callbacks and wrap it in another object, and that's what it actually ends up calling each time, that's the actual listener, and forwards on to your, if you don't type in, it always runs, yes. And if you don't return anything, then I believe that also says, I didn't handle it, so I'm done. If you do return something, then the first one to return wins and the rest don't get called. So you don't need to do, you know, events, arrow, set response, like you would in a symphony listener. This is just a shorthand for that, but that's exactly what's happening under, yeah, the exception listener and the view listener in symphony work almost exactly the same. Short cuts like this for the request event in symphony, called before, for the response event in symphony called after, and view doesn't have one, but there's an open issue right now where it hopefully should get in the next release, because it's silly for it to not have one, so I hope it gets in, and hopefully it will work the exact same way as exception, if I win. There's an open discussion in a GitHub thread right now, but I'm pretty sure we'll be able to get that in with the type hinting for view as well, because that's really, really powerful, as we'll see in a moment. In fact, that's an excellent segue. We're sending out a REST API, which means we should be having hypermedia links. You didn't know this was also a talk on REST, not just on SILX, right? Someone told you? Okay. We should be sending out hypermedia links, which there's about eight different formats for in JSON, because JSON is a terrible format. It's really easy to parse, but it actually doesn't work well as a REST format, because it doesn't understand the concept of links, so there's a dozen competing formats for it. Go team! XML does actually have a standard for that, more or less. So, what we'll do instead here, so we're going to use a library called NoCarrierHAL. It's just a, again, random PHP library off of packages that is the most commonly used in PHP for HAL. HAL stands for Hypertext Application Language. It's probably the leading format right now for hypermedia with JSON. It's an IETF draft spec. It's also what is used by default by Drupal 8 and by default by ZendApagility. So, it's a good format to know. It's very simple. You take any arbitrary JSON object and you just take a magic property on it called links, and another one called embed, and those have a defined structure to them. I'm not going to go into that in too much detail other than to say, we're going to return not a string, not a response, but a HAL object from our controller by a HAL object, because I'm just returning the data. I'm not responsible for formatting it. Who here knows Paul Jones? He's a PHP developer. Google for something called Action Domain Responder. It's something he's been pushing recently as an architectural pattern similar to what people incorrectly call MVC. That actually fits the web a lot better, where incoming requests goes to an action, which processes that request, but then something else is responsible for formatting a response. Because those are two separate actions. It's not really a MVC type structure, necessarily. And this actually fits really, really well with Symphony's HTTP kernel. This is how Drupal 8 is working. This is how you should be doing things in Symphony and Sylex. Fabian actually says don't use the view listener because it's slower. It is sometimes, but I find the architectural separation that it gives you really, really nice. So our controller here is now going to return this thing that is not a response and not a string. So do we know what to do with that? We registered some listeners to the view event. Syntax here is a bit clunkier. As I said, there is no wrapper for view yet. There will be soon, I hope, the more generic on the various events, kernel events view. And we have to do it the clunky way. So we actually have a response object, event object. We get the control result off of it. And this is now exactly the same as in any other Symphony app. If the result that came back from the controller is an instance of that HAL object, then we'll handle it. If it's not, we won't, and whoever comes next will take care of it. So if the request is JSON or HAL JSON, that's the format. Then we'll wrap that in our own response object that formats it as HAL. We can set it to format the JSON nicely if we're in debug mode so that it's much easier for me to read the JSON rather than it being one long string. If it's not, if the request was not one of those formats, one of those HAL JSON formats, then we'll say, all right, if we're in debug mode, you're probably just typing stuff into your browser. So I'll go ahead and return it anyway and I'll assume you want JSON back. But I'm going to send a response type of application JSON, not HAL JSON, because browsers are stupid and if you send back a response of application JSON, they'll print it as text. If you send back a JSON variant, they won't know what to do with it and they'll ask you to save the file. So this just makes debugging easier. But if we're in production and the request format is not JSON or HAL JSON, then we'll throw an appropriate exception, not acceptable, wrong mind type and that could convert into a response by Sylex and you get the nice error message back. Actually, we're handling that ourselves, so no acceptable format found. How do we know about that HAL JSON? Well, we also are registering some before listeners. We're doing content negotiation. Please don't do content negotiation this blindly. This is not a great implementation, but it does work. Just get the acceptable content types out of the request. That's what content types did that request say were acceptable in the accept header. Check against the available formats if it's one of the machine name formats that Sylex supports and Symphony supports. Specify that and we're going to default to JSON if we don't know what else to do. Is this the right way to handle it? It's not very robust, but it fits on one screen, so I'm good with it. So, request comes in, we do content negotiation, routing happens, maps off to our controller. Controller returns a HAL object, view listener turns that HAL object into a JSON response and we're good. We've written all of this ourselves and it's only taken us 45 minutes. One last thing, caching. It's kind of important. Sylex does absolutely no caching on the PHP side because you probably don't need it. It's lightweight enough that it's not a big deal and if you get to the point that you need it, well you can implement it yourself. Or move your app over to Symphony where it does a lot more pre-building of stuff. But Symphony doesn't do render caching either. Caching of the output is HTTP's job. HTTP already takes care of caching for you. You should let it. You just have to give it the right headers. What are those right headers? Let's register some after listeners. This is the response event. If we're in debug mode, disable all caching. Great. We've got a cache lifetime configuration property we allow. We'll default to zero. We'll just set on the response, hey, our cache lifetime is 3,600 seconds. Five minutes. So, they time to live on the cache for both proxies and for the browser's cache is five minutes. You can vary those separately if you feel like it. I'm going to set them the same, whatever. Set an expires header as well for older clients. And set an e-tag. An e-tag is simply some unique hash that indicates if this has changed, that means the output has changed and so you need to get it again. This is part of the HTTP one spec. This is not, again, this is not necessarily the most robust way of generating an e-tag, but it works. And the challenge is I can't just invalidate the cache when someone updates an object because you've got these links between them. And so those links could vary when the object doesn't. So I'm going to take the cheap way out and run through the entire process and at the end say, alright, now that I know what the response is going to be, does the SHA-1 match the e-tag that the browser sent? Saying, hey, last time I requested this, here's the e-tag I got back. I've still got that version. If it hasn't changed, just let me know. And then we can just ask symphony. Hey, symphony, or Sylex, symphony library, if given that request and the response I'm about to send, nothing's actually changed, then send a not modified, a 304 not modified instead. And symphony will chop off the body, change the response type to 304 not modified, and send it back, and you are sending back just HTTP headers, no actual body, saving the network. You've got varnish sitting between you and the client. Great, it'll cache stuff for however long you tell it to. It may even handle some e-tag stuff. This is all off-loading cache logic to some HTTP server that knows what it's doing with that. Nginx, varnish, a CDN, take your pick. They do it better than you do, and they are faster than PHP, let them. You've implemented a cached Twitter clone with proper rest semantics in Sylex in 56 minutes. Thank you. So we've got about four minutes for questions. Yes, the question was, doing e-tags this way doesn't actually save us time in PHP. That's correct. If you want, you can put this same logic in the, in a before listener, aka request listener. If your logic for knowing what the e-tag is supposed to be can be done before you get to the controller. If you know that, great. Move this logic to a before listener, and you can just, you know, check there and say, oh, response, create a response, not modified, return that, bypass everything else. Same thing works in symphony, same thing works in Drupal 8, all exactly the same. In this case, again, because of the cross-linking, it's possible that the response that comes back at one URL, the actual bytes may change, even if the underlying object doesn't, because of the links. Like if I, I'm linking to another object, and I include its name, that object changes, I change too. So that kind of cache invalidation is hard. I decided I didn't want to bother with that here. If your application can do that, by all means, you know, save yourself the, the CPU. But this at least does save network traffic and browser speed, or client speed. Can you use annotation for routing? I haven't. There may be a provider that will do that kind of scan. It would then have to cache it as well, somewhere, otherwise it'd be way too slow. I don't know. There's probably a mechanism for it, but I haven't used it. I prefer to just go old school here. Which does bring up an interesting point. Because Sylex doesn't do the compilation by default, that symphony does. That means it does have, you know, it's much more like wait on an individual request, but it does have a scaling issue. If you have an app that has 400 services and 80 routes, those have to get re-registered on every single request. I understand there are some dumping tools for Sylex. I have not used any of them. Or at that point you convert your app over to symphony or Drupal 8 or whatever. Should be easy if you've done your job right, i.e. if your logic is all over in services, you copy those over into a Drupal 8 site, into a Sylex framework, whatever. Tweak your, you know, controller a little bit, tweak your routes a little bit. It could take you an afternoon support to your app from Sylex Symphony to Drupal 8 if you've done it well. You don't think that microphone's on. The question is for someone new to symphony, is this a good place to start for a small app? Yes, I did. Because it has so little stuff built into it, it makes it much easier to look at just the bare bones core pipeline. Febein recommends that as well. If you want to learn how symphony works under the hood, use Sylex. Because it's the same kernel that Sylex, that symphony uses, that Drupal 8 uses, that various other things use. But all of the stuff on top of it is stripped away. So it is actually a very good symphony learning tool. Again, it works for an awful lot of systems. You can download, uh, Twig Provider, there's a Twig Provider for it. You download Twig, you can build pages with Twig instead. For pure API stuff, I really like Sylex. I, I actually kind of like not having all of the conceptual layers of overhead that symphony has. That said, I also work on Drupal 8, so go figure. Point is, yeah, it's a perfectly viable option if it'll do what you want. And one of the advantages of all, all of these systems using the same common component is your same team can jump from Sylex to symphony to Drupal 8 and back with relatively little retraining. Because all you need to learn is the stuff on top of that common pipeline. The common pipeline is going to be the same on all three. So if you think to yourself, oh, I would do this in a request listener in symphony, that, that answer is going to be the same in Drupal 8 or in Sylex. Say, oh, I'm going to do this weird logic with, you know, grabbing the response, doing some regex on the string for whatever reason. You can take that code and move it almost for a beta from Drupal 8 over to symphony. And, you know, all three of those systems can interchange to a large degree. So that makes it a lot easier for teams to cross train in all three of them. Okay, time for one more question before they throw me out. All right, great timing. Thank you. Enjoy the rest of the conference.