 We're going to start our first talk, given by Patrick Kirish, and he's going to talk about specifications. Thanks, Ma. Wim. Yeah, okay, hi. Specification pattern. Well, my talk is called boost up your code with specifications, but anyone heard about about specification pattern or specifications? Okay, not much. So, what are specifications? What are these? We go to specifications later, first we should tackle the problem. Who is working with doctrine, protocol or anything else? Not much. So, the other half is doing SQL statements on your own with PDO, at least. My SQL comments. Oh, yeah. I hope my SQL, you don't have my SQL comments on. Okay, so, an ORM. What's an ORM? It's an object-relational-mapper, and an object-relational-mapper maps your PHP object or model or entity, however you want to call it. And doctrine is called entity to database table, to simply database row table. So, let's take a look at an example model. In this case, we declare class unicorn, which has an ID. We always need an ID and primary key and unique identifier for database. And we have a name, of course. A unicorn should have a name and a color. Why not? Birthday, okay? And it has laser horns. Maybe not. It could be true or false. And it poops rainbows and it can fly, because unicorns must fly. So, this is an example, PHP class. It's simple. And the ORM configuration for doctrine, protocol, however, isn't shown yet because it doesn't matter today. But, if you have a unicorn class or any model you use, you want to save it to the database. So, use your ORM to create this table with your IDs and int, varchar, tiny int for the booleans. So, let's take a look at the clause. This is clause. It's an awesome unicorn. So, we'll prepare this health clause. It's white? I don't know what it has. It can't poop rainbows, but it has not a laser horn, sadly. It would be more cruel, but... So, we define clause, we create a new unicorn instance, name it clause, it's white, it's 30 years old. Please congratulate clause, it's birthday today. And, yeah, no laser horn. It poops rainbows and can fly. So, now we save it to the database. In this case, I use doctrine to parse it, and that's all. And this is clause saved to the database. Simple select on my SQL. So, that's the basics of an ORM. And, now to the problem. In an ORM, you have repositories. Who is writing repositories with advanced methods of you? Yeah, not that many. What is an repository? The official definition is mediates between the domain and data mapping layers using a collection like interface for exics and domain objects, blah, blah, blah. Yeah. It's the official definition by Martin Fowler. So, a repository class. It's a central class for your unicorn model, for any other entity with all the find-by methods, find-by-one, etc. So, you don't use query-builders in your controllers in your services. You shouldn't do that. Who is doing that? Who creates query-builders in the controller? Sometimes. It doesn't matter. And, you can reuse criteria codes for your parts of the query-builder within the repository class. So, but the problem of a repository class, it can grow. Let's take a look. You can have a find-one-by-D. So, you can find Klaus by his ID-one. You could find it by his name. To find Klaus by Klaus. Yeah, by his name. You can find all unicorns by a specific color or find unicorns in its herald. So, we have an occasion here to the herald or find a unicorn which can put rainbows or not. Depends of the parameter of the method. Or, you can find one by herald and name. So, you are searching Klaus, but you will only find Klaus in the lower herald. And, you can find unicorns by herald and color. So, you won't find all green unicorns, but only in this specific herd. Not every green unicorn. You can find by color with laser horns. And so on, and so on. Who has counted the methods? No? Eight years. And that's not all. Every time you need another criteria, another combination, and so on, you have to create a new method, maybe a refactor code, and so on, and so on. Maybe 15, 20, 25 methods in a big domain model. That sucks. That really sucks. So, how we can do this? We use specifications. So, what are specifications again? It's called more called a specification pattern. And, again, the central idea of specification is to separate the statement from the candidate object. It's, yeah, still, same definition. So, separate the statement from the candidate object. That's the essence of the specification. So, you separate your search methods defined by one from the candidate object from the unicorn away. So, specifications are, in the sense, they are composable. You can combine them, like you have done before in the Ripple Story, but specifications should be re-useable. So, you can reuse them. We will see later. And the important point is, specifications should be test-able. Such big Ripple Stories with 25 methods and so on are not really test-able. So, back to Klaus. We define a business rule for today. An awesome unicorns. The awesome unicorn can fly, of course, like Klaus does. It put rainbows in the first white. That's an awesome unicorn, and that is a business rule. And to define this specification, sorry, this business rule into a specification, we can create a simple class called awesome unicorn, which implements a specification interface. The specification interface is yours. The only method it has is is satisfied by. In this case, we are using is satisfied by a unicorn instance and can detest the unicorn instance on its method, can it fly, can it put rainbows, and if the color is white, it either returns true or false. That's all about the specification class. Any questions to it? Does it look simple enough for specification class? That's fine. The only part is we get the unicorn instance, which means it is testable, but not reusable, because we only have a unicorn instance. You want to test a turtle against an... Okay, awesome unicorn, you won't test turtles, but you can't test it on a... Yeah, you can't test it on a turtle. And, of course, it's not composable. How you can combine it with and or. So, let's add some composed methods, like andX, so we combine our specifications, this with an and specification class to the sublite specification. Or we can use an orX method and not... This is how you can compose specification classes. You create another specification, and specification, or specification, negation specification, combines another specification classes. How we can use it? It's simple. We create a new awesome unicorn specification and combine it with a youngster specification and, of course, with a new laser horn specification, not. It's not naturally readable because the negation is afterwards, but it's more readable than SQL statements or repositories. And, to test, if this unicorn instance satisfies this specification and is an awesome young unicorn, we simply supply you the unicorn-to-day-satisfied method. That's all. In this case, maybe it's an awesome young unicorn. Yeah, the bros of this. It's solid. It's a simple class. It's reusable. It is separated from the Ripper stores. It's separated from your models. It's unit-testable. Or anyone disagree with that simple class. It's unit-testable. And, of course, composable. Maybe you're asking why we should add nx or x or not methods. We could do this outside with a if too. Yeah, it depends how you like it. Okay, it still has some cons. It could be clearer. Writing big ifs in a return statement is not that nice. And, of course, as I said, it's only usable on a unicorn instance. So, specification pattern is cool by writing it on your own sucks. So, use rulers. Rulers is a real awesome library. Simply install it with Composer and the features is it doesn't use if statements. It uses a data agnostic dsl domain-specific language to express our business rules. Which means you write the instance level as we have done before. And now it gets awesome. It works directly at the data source level with Docker, and Pro, and so on. So, first, let's instantiate a ruler's engine. We need a file compiler because somehow has to parse our business rule. This is the compiler. This doesn't matter what you use. You can use only the hover parser at this time. Or you prefer you will define your cache here. You need to clear it after deployments or if you have changed the business rules. And then we create a new ruler with the compiler. Say we want to super dog ring veribillers and we want to super array visitor. We see later what's the difference between these two. Okay. Anyone using symphony? Cool. So, require the ruler's panel. Configure it. Arrays supported by default. But if you want to use dog ring, configure it to use it, that's all. And get rulers from the service container. Normal stuff to do. Cache directory is configured to the symphony cache. It's cleared with the symphony cache and so on. So, let's get back to our awesome Unicom business rule. Yeah. Here we have the rule. It doesn't look like the class before. It doesn't matter now. So we have the rule. Can fly is true. And poops rainbows is true. And color is prepared parameter color. Looks like SQL, does it? Not that much difference. That's nice because it's the main specific language. It's not really SQL if you use custom function and so on. But if any developer looks at it, each developer can understand it. And that's really nice. So, we ask rulers if the Unicom instance satisfies this rule. Of course, we have to sublet the parameter for color. It's white. We could encode here. We could use the color and write it statically here in the rule. It depends on how you prefer it. And rulers says, is it true? Does it satisfy this rule or not? But you don't want to clutter your code with rules, business rules all over your code base. So, again, we create a specification class. In this case, it's an awesome Unicom class which extends an abstract specification. This is a class supplied by rulers. You could also implement the specification interface again. But the abstract specification has some methods for you, like helping you like the NX or X and not. And again, here we have our business rule. This is the only position in your code base where this business rule is defined. In any other part of your code, you use the specification class. Thanks. Okay, still stuck with this microphone. Yeah, same rule, same parameters, color white. And this, as I said, is the diagnostic DSL to express our business rule. For every business rule, for every rule you want to use, you create one specification class, write the rule, write parameters. Of course, this can be an internal property supplied by the constructor. No problem. It's the same usage. We create a new awesome Unicon instance and define it with a youngster and with a laser horn knot. But now we ask rulers, is the Unicon instance, is the specification satisfied by this Unicon instance? So is it on some young Unicon or not? Rulers tells us. So it works on the instance level. But if you have big database of Unicrons, thousands, ten thousands, you don't want to fetch ten thousands of Unicon in the memory. You want to filter on the SQL level, on the database level directly. No problem. In this case with Doctrine, we create a query builder, select Unicorn from the Unicorn table and ask rulers to filter this query builder with our specification and we get back an array of Unicrons which satisfies our specifications. So in this case, rulers is building a query builder for you out from your business rules. And it works on the data source level which means performance, performance, performance, performance. You don't want to fetch ten thousand Unicons in the memory as said. Okay. Everything as promised before. But we use ability. In this case, we have a name specification and a rainbow pooping specification which we combine. We are asking rulers to filter our query builder against our spec and get to the first one and we have Klaus because it's Klaus and Klaus poops rainbows, of course. And now we get the herd of Klaus. It's a simple Doctrine association and ask rulers to filter against the rainbow pooping specification against the herd which is returned by Klaus. So the same specification, rainbow pooping spec here and rainbow pooping spec here is used against a Unicorn instance and a Unicorn data source and herd instances. So it's reusable on every array query builder instance of a Unicorn. It doesn't matter where you use the specification, you can use it. We create it only once and use it on multiple sources. In this case, I was sorry, the herd returns still an array of Unicorns. So we reuse it on a query builder, reuse it on an array of Unicorns. Okay, the same goes reusability on different types. We have a names spec Klaus. Klaus could be a Unicorn so we get an array of Unicorns named Klaus or Klaus could be a turtle. So we are filtering an array of turtles to get all turtles which are called Klaus. The same specification is used on different instance types arrays. So it's reusable on any class with the same properties which are asked in the business rule. Of course, in this case you have to design your domain, your model's query. You can't have name and title in two different properties and expect to reuse your specifications. As seen, this is our name specification. We have to get rule with name is of course the name and return the parameter to remember this name. It comes from a constructor. It doesn't matter. And Klaus Unicorn has a name property and turtle must have the same name property. Otherwise it wouldn't work, especially on some fields like birth date or date of birth. This is really important to design your domain. Another cool feature of rulers are custom functions. In this case, I created an age specification, age stack, where the rule is birth date is over age and age is the parameter supplied. So I want to do it simple. I don't want to do date time math in my daily code base. So the date time math is done by the custom function age. And it's a custom function, so we must define it for every executor, array visitor, doctrine builder, and so on. Let's take a look how we do it. This is an array age operator. It's simple call bake class with invoke, where we get the age, it returns the date time and simply returns the date time from the A minus age years. So if it's up to 30, we get the date time, the exact date time 30 years ago. And this age operator, simply defined on an array visitor, age is the new array age operator. So if we have the business rule before, with the age, we can now use this rule with arrays of unicorns, of turtles, etc. Of course. But we don't have only arrays to filter, we also have a doctrine, we rebuild it to filter. So we have to create an array age operator for doctrine. It's simple, with an doctrine age operator. And in this case, we have to do some doctrine math. If you don't know DQL from doctrine, it doesn't matter. In this case, I use the current date and subtract the age times 12 as months from the current date. Doctrine doesn't support subtracting years, so I have to calculate with months. And this is directly a DQL statement returned for doctrine, we rebuild it. In this case, because it's DQL, we have to do an inline operator, HNU, and so on, and simply to the rule of engine. It's only important for you if you want to create custom functions, but it's nice to get help with custom functions in the business rule. Don't do date time math every time. Maybe you want to use string length in your business rule. There are many possibilities why you want the custom operator. Yeah, joins. Joins are nice. So in this case, we have a herd specification, which is basically asking the herd property on the unicorn. It's for a unicorn. And the unicorn has an herd classification and asking the name of the azucated herd is the same name I'm searching for. And this is how you use it. You simply create a unicorn variable as before. Rulers automatically detects you are asking for a join if your mapping is okay and filters all herds in this case, which is named Adams. Maybe you have a unicorn herd named Adams family. Yeah. Of course, this works only for doctrine ORM, not for prople or Laravel eloquent. Maybe you can create a pull request for it, but it's nice. In this case, it's auto-detected, but you can also supply inner join, left join, right join if you want, and rulers use your defined join too. Any questions to rulers so far? Yeah? What about getters and setters? Okay, the question is why I didn't use getters and setters? Because it's an example. No, I'm saying does it match the limitation? Does it match by the name? No. Basically, under the hood, it uses symphony prepare the accessor, it works with public preparedness, and it works with getters, setters, if they are present. That's not a problem. It was only for the example to keep the coat shut. Yeah. Not a question. So basically, yeah. Pardon? Group is not supported. I created some special function to group that, but it was really ugly to do that. So let's take a look under the hood. This is the basic architecture of rulers, which means your rule gets passed by a browser and combined into intermediate representation or called abstract syntax read by an awesome library who are compiler. And this intermediate representation is compiled to PHP code, SQL queries, elastic search queries, which means on runtime, nothing is passed again. If your rule is passed once, it's compiled effectively to PHP code. For example, this is our awesome unicorn rule, and it flies through and pulls rainbows and so on. It's compiled to PHP code. And this is reused every time you use the awesome unicorn specification. So don't worry if you write big strings as business rule. It's really performant because it gets compiled. And here you can see we use array access for you. And in the back end, this is symphony property accessor. So it doesn't matter if you use getters, setters, or public methods, because if you have a simply domain transfer object, you don't need getters and setters. And this is the cache doctrine target of our herdspec. So rulers detected the joins to the herd association and uses this with get join alias target herd herd dot name and so on and so on. Yeah. So available targets. We have seen some of them already. You can fill down an array of arrays, which mean you don't have to grade objects or models. Unicorn could also be a two-dimensional array with array keys as the properties. Yeah. Array of objects as we have already had, an array of unicorn instances or turtle instances, doctrine or inquiry billers. And now you get awesome for who are using doctrine. You can still use POM and fill the POM models. Or for the Laravel guys, you can filter with elegant. So for every one of you, it should work. And about reusability. It's really nice. You can filter elastic search and solar servers. So you have one specification where you can first look into elastic search, like a product specification. Searching for specific product. Search in elastic search because it's more performant than to search in MySQL. If you don't find anything, then search in the database with the same rule. It doesn't matter the data source you use. You always use the same specification class to filter your stuff. Yeah. And of course, you can build your own executor, like in our company we use. Yeah. Sometimes everyone wants to write his own ORM. We have one in our company, so I had to grade an executor to filter our own ORM. And it's ugly. I mean, our ORM is ugly. Grating on visitor for your custom stuff is nice. Maybe if you have a graph API, you can grade and execute the filter. Graph API. That would be cool if anyone does. So I'm going fast. Yeah. A few use cases. We can look at the repository before. We can find one by ID, by name, and so on, which are one, two, three, four, five, six, seven, eight, nine, ten methods by now and many more. We don't want to use this because we have tamed the repositories with rulers, which means we have one matching method on the repository. Matching against a specification. Any specification you have. It doesn't matter. Of course, the specification you use here must match against the model with the rule. Otherwise you get an exception. And we create a query builder, Unicorn local, and of course, in this case, it looks like a Unicorn repository has to be a service. No problem. Because we have this ruler's instance. And asking rulers to filter our Internet query builder against the sublet specification. That's all. Not more. Every Europe's story can look like this. Okay. But we still have a little problem here. And one can think about the problem here. No. This is top of doctrine. Of course, but that's part of the main modeling. In this case, what if you have one million Unicorns in the database and have a really simple specification? Maybe you want to get all rainbow pooping once. And if you have one million Unicorns, maybe half of them can poop rainbows. And you get a result of half a million Unicorns. That's the problem. In this case, this is simple as fuck. It's really simple. Three lines. And that's all. Back to one million Unicorns. In this case, I want to use pagination. So I create the query builder and ask ruler to apply my filter specification on it and get back, again, a query builder. And not the result already. Now, I can do with the query builder anything I want. In this case, I set the first result, set max result to do pagination, to apply a doctrine paginate on it. And now performance problem is, there's no performance problem anymore if you use pagination. Because on every side you use pagination. You want, maybe if you have a list view, you want to show only 100 Unicorns and then the next 100 Unicorns and not half a million ones. So you need a paginator and that's not a problem because instead of filters back, you use apply filters back and get back your query builder. This works for Laravel and POM2. Another example where I get into touch with rulers was this search form for a retailer search. We are searching for the country and the country region or NCIP code. And we have a search for a keyword and some special assortments of the products. And in this case I used rulers to map each of the form field to one specification object. Because country, country maybe is a simple rule. Country zone is a complex rule with joins and so on. So I could combine a country specification and country zone specification or keyword specification. The code, the controller code, the filter service looks really small because the big business world of each field is encapsulated in a specification class. And what's nice again? Specification is a class. So why a specification shouldn't be an entity too? So for the assortments, I have an assortment entity, assortment model, which is our own ORM, doesn't know, and implements a specification, which means it is saved into the database and now CMS, the editor, can define the business rules. This is saved in these rules. It's the textual business rule saved in the database by the CMS editors. And name doesn't matter. It's for the backend. So yeah, name rules. It comes from the backend. It comes from the database. Yours, business rules from the database. Why not? So the local part is, the local ID is in set of assortments. This in set is a custom function. And of course, if the rules aren't empty from the editor, we combine it with ORM and return it. So we have a specification from the database from the editors. That's nice. And this is used for the assortments. So our CMS editors can create, add, and move assortments to filter the retailers on this map search. Another nice use case is Wallabag. Who has heard about Wallabag? Nice. Wallabag is like, yeah, it is used to save articles on the internet and you can't read it here really. It has some tags. And these tags are taking rules from rulers. So reading time is lower equals five. This is a rulers business rule. So here again, you define business rules in the database and use it then as specifications. Nobody could create one an held up visitor. Would it be nice? But would it be your use case? That's a nice idea. Maybe you can create a visitor and supply it to rulers. Okay. I was going fast. Too fast seems. But now we have plenty of time for questions. What exactly is the unit test here? I mean, because we started with unit testing, then we started fetching data from DB and then we ended up storing the rules in the DB. So what do you exactly unit test here or do you test? Or not? Sadly, we can't unit test these rules. We do integration tests because you can unit test a rule against a race but not against the data source. There you have to do integration tests. So we are one level higher than the integration behavior testing level. Yeah? You wanted to introduce caching in the middle. Where would you put caching? If we want to introduce caching in the middle, what do you mean with caching in the middle? Caching would just better. If I understand the question correctly, you can still use, for example, doctrine query cache result cache. That's no problem. Because apply filter spec, you get the query builder and you can use result cache and so on. And the other caching layer would be in the controller, the page cache of the controller. And what is already cached is the business rule which gets compiled down to PHP code, DQL queries, Elasticsearch queries. This is already cached. But this is not a cached result, it's the cached business rule. If you want to add query to other results, with the result, you can do anything you want because the result is mostly an array of your instances. Like ordering, sorry. Sorry. Order by. Yep. How you wanted to use query builder at the order by method and then filter or apply filter spec and then use the query builder to add order by group by and so on. Ordering, grouping is not defined in the business rule. That's a matter of your repository again. So you are filtering the query builder or apply a filter on the query builder for filtering with this business rule but ordering, grouping, having or whatever limiting pagination is a case of your query builder. They get compiled on the first execution. There's no warm-up script to fetch all your specification, the code base and compile it beforehand. So for also a unicorn specification, the compiled PHP code is created on the first time you filter on this specification but then it's reused every time. Yep. After the first execution, each compiled specification is reused. That's the part of the performance. Another some questions. So please, you find the slides on FOSTEM specs and please give feedback on joining and maybe for the FOSTEM conference FOSTEM minus feedback. So, thank you. As this was fast, you can all get on your Twitter and follow me and, yeah, take a look at our company site and so on. Thanks.