4.1 Chain of Responsibility
In this lesson, you'll use chain of responsibility (CoR) to develop a processing pipeline to write messages to multiple log types.
1.Introduction1 lesson, 01:40
2.Creational Patterns2 lessons, 11:52
3.Structural Patterns4 lessons, 33:47
4.Behavior Patterns4 lessons, 36:32
4.1Chain of Responsibility12:12
5.Conclusion1 lesson, 01:06
4.1 Chain of Responsibility
The chain of responsibility pattern can be a little confusing, partly because people call it different things. The traditional name as chain of responsibility, but other people call it the chain of command pattern, so there is that. There's also the fact that you can implement this in a variety of different ways, and that just adds to the confusion. But in this lesson we are going to look at a more traditional approach. Now, the idea behind the chain of responsibility is that we have a chain of handlers. And we use these handlers to process something. I am going to call that something a request. So, we have a chain of handlers and then they are going to be processing a request. A request isn't necessarily an HTTP request, the request is just some information that's going to be processed. However with saying that if you're familiar with Laravelle or some other modern server side technologies like Node or ASP.NET core then you've heard the term middleware. Middleware is an implementation of the chain of responsibility pattern, it's a pipeline basically. An HTTP request is handled by each piece of middleware and that piece of middleware determines if it can process the request. If it can then it does. It can also determine if it is going to stop processing altogether. So for example, Larvelle has a piece of middleware that determines if the user is logged in. If they are not then it's going to stop processing. It redirects the user to the log in page. However if the user is logged in then it passes the request on to the next piece of middleware that's going to do whatever it needs to do. So that's the idea behind the chain of responsibility. In this lesson we are going to implement a logging system. And the first thing that we are going to define is an abstract class that's going to serve as an enum. Cuz we're going to have different types of logs. We're going to have an information log which is not very important. Each one of these log's is going to have a level associated with it, and that's kind of the severity of the type of log. So the information, not very severe. However, the next log is going to be our debug log. And this is a little bit more severe, so all of our debug information is not only going to be written to the debug log, but it's also going to be written to the information log as well. So the higher the severity, the more places those messages actually get logged. And the third level is going to be our error. We're going to set this with a level of two. So if we log something as an error it's going to go to the error log. It's also going to go to the debug log, and then it's going to go into the information log. So it just kinda trickles down in that sense. However if we have something written to the information log, it's just going to stay at the information log. There is no sense that it goes to debug or error. So we are going to have different places that we are going to write the information to. The first is going to be the Consolelog. The second is going to be a file. So we'll call that Filelog. And then the third is going to be our operating system's Eventlog. So we're going to tie into our operating system's event system so that we can log whatever information that we need to. Now we're not actually going to do that, but that's the idea that we're doing here. And every time we create one of this logs we need to know the log level that it is going to have. So let's create a constructor for each one of this classes. And we'll just have log level. And inside of these constructors we are going to set a level property equal to whatever was provided to the constructor. So, that's going to be logLevel, and we're just going to copy and paste this in all of the other classes. And so, let's go ahead and create these logs, the first one is going to be the console logs. And this is going to be for our information log. So, let's call this console, and we will set that equal to new console log, we will pass in (logtype::Information). And then the next one is going to be our debug log. So let's just copy and paste a couple of times. We'll call this $debug, we'll call the third one $error and then we will change the constructor names. This will be Filelog, the error will be the Eventlog. And then we also need to change the LogType. Now, there's one other thing that we need to do to these classes. They need a method for actually writing to their respective logs. So we're going to make this protected. And we'll just call this method write. We will accept a string, and we will call that $message. And we're just going to echo this out, but we're going to say that this is for the console. And then we will have our message, and so we will do the same thing for the other classes. But of course instead of saying console, we're going to say file in this case. And then inside of the event log, we are going to say event. So these are the methods that we will use to actually write to those logs. Now right now these are just dejointed, but what we are going to do is write an abstract class that these are going to extend. And so, let's call that Abstractlog and we're going to need two properties. The fist is going to be protected $level;. So that we know what that level is. We were setting that property inside of the constructor. The second is going to be the next logger that's going to be in the chain. So we're just going to call that next logger. Let's go ahead an initialize that as null. Now we also need a method for setting the next logger. So let's call that setNext. Now in this case, we're going to say that this is going to be an abstract log. So we're going to use a typeInt here and we're just going to call that logger. And then inside of this method we will set our nextLogger equal to the logger that was provided as a parameter. The next thing we're going to define is a protected abstract function called write. So this is where that write method is going to come into play. And this is protected, it's not going to be available outside of the class. So we need a public facing method to actually perform the action of writing to the log. So let's just call it log. And we need two things, the first is the log that we are going to write to. The second is the message that we want to write to that log. Now if you'll remember, the stuff that's written to the error log is going to trickle down into the other logs. And the same is true for debug. It's going to trickle down to information. So everything trickles down, nothing trickles up. So inside of the log method we need to check to see what this log's level is. Is if it is less than or equal to the level that's provided as the parameter, well then we want to write this to the log. So we will use the write method, we will pass in our message, and we should be good to go as far as that is concerned. However we also want to pass on this request the next logger in the chain. So we're going to check to see if next logger is not null. And if it's not, then we are going to pass it on. And so we will say next logger, we're going to call it log method, we will pass in the level and the message. So this code is for the trickle down, this code is passing on the message to the next logger in the chain. So, now we just need something that we can use to write our logs with, and we're going to call this class AppLog. And inside of its constructor, we are essentially going to do what we did a few lines below. And that is create our logger objects. And then we will set up the chain. So let's just move that code into the constructor and then we'll setup the chain. We'll say console, we'll the setNext method, and we will pass in debug. And then we will do the same thing starting with debug. And then adding error as the next item, and that sets up the chain. Now we just need to keep track of the starting point of our chain. So let's just call this logChain = and the starting point is our $console, and let's define that private field $logChain. And now we just need a method for writing to our log. So, we will write another log method. And we want the level or the log that we're going to write to. We also want the message that we want to write. And then we're going just going to say this log chain, we're going to call the log method, we'll pass in the level and the message. And that'll kick everything off. So we do need to extend our abstract class for these other classes. So we'll take our console log, extends Abstractlog, and we will do that for our other classes. So FileLog and EventLog and now we just need to create our $app log object, so we will create an. A new AppLog(); and then we will say app >log will start with information log, so Information, and the message is going to be this is information. So, whenever this code executes, we should see just one line that says this is information. And then we will write to the debug log, and we should see this two times. We should see it for the File, and then for the console, because remember that trickles down. And then we will have the error, and so the error we should see three times. One for event, one for file, and one for console. So there's a lot of setup involved here, but it pays off whenever you actually use it. So let's head on over to the console, and we are going to run this file and hopefully everything works, it does. So, we have our information that's being written to the console, that just happens once. We have our debug that is written to the file and the console. Then we have our error, which is written to all three. So the chain of our responsibility pattern is useful when you want to process the same thing with multiple processors or handlers. It's a pattern that you're not going to use as often as many other patterns. But it still has it's uses.