Skip to main content

Traits in PHP

What are traits?

Image: Pixabay
Let's start by understanding what traits are and how they're useful.  We'll move on to code examples straight after that.

Traits are not unique to PHP and are available in other languages too.  They provide a way to extend the functionality of a class.  A trait will have methods to implement this functionality and make these available as if they had been defined in the class itself.

In other words traits are flattened into a class and it doesn’t matter if a method is defined in the trait or in the class that uses the trait. You could copy and paste the code from the trait into the class and it would be used in the same manner.

The code that is included into a trait is intended to encapsulate reusable properties and methods that
can be applied to multiple classes.  Traits group functionality in a fine-grained and consistent way and allow you to reuse this functionality without requiring inheritance.

I mentioned before in the lecture on inheritance that PHP does not support inheriting from multiple classes.  Traits offer a way to provide similar functionality and offer a way to avoid the so-called "diamond problem" that is introduced with multiple inheritance.

Defining and using traits

Traits are very easy to use and there are lots of places that they can be useful.  I'm going to run through an example that defines a trait that implements singleton functionality.



We define the trait using the `trait` keyword followed by the name we want to use.  We can declare variables in the trait as well as functions.  

If you look at the code example you'll see that my Singleton trait has got all of the implementation requirements for the singleton pattern.

I've defined a private static variable called $instance in the trait.  Although it's private in the trait remember that the code gets flattened when a class uses the trait and so the class would be able to access it.  If that's not clear just imagine copying and pasting the code in the trait straight into your class.

The trait also includes a static function called getInstance that returns a singleton instance of the class.

I'm using the trait in the class called MyClass.  The `use` keyword indicates that we're using the trait.  You might already be familiar with the `use` keyword as a way to import namespaces.  When we import a namespace the `use` keyword is outside of the class, if it's inside the class it indicates that we're using a trait.

Now we're able to call the `getInstance()` static method on the class.  This method is defined in the trait but we're using the trait in the class so it's available to be used as part of the class.

We're able to overwrite the constructor method in the trait.  In this example I've set the constructor to private so that you are not able to instantiate the class but rather have to use the singleton method to get an instance.  Trying to create a new instance of MyClass generates a fatal error because `__construct()` is public.

Now we're able to easily give a class all of the functionality it needs to be turned into a singleton without interfering with the inheritance hierarchy.

Namespacing Traits

So far we've seen how to declare and use a trait.  What happens if we want to use a name for our trait that has already been using elsewhere in our code?  It might happen for example that one of the libraries we use has already defined a trait named Singleton.

Traits, just like classes, can be namespaced.  The name of a trait must be unique within its namespace otherwise PHP will throw a fatal error.

Just as with classes if you're wanting to use a trait in a namespace other than your own then you'll need to provide a full reference to it.

Inheritance and Method precedence

Traits may not inherit from other traits or classes but you can nest one trait inside another.

Okay so inheritance is simple, but what about situations where a class and trait have a method with the same name?  Which method will take precedence and be run when we try to call it?

Methods declared in a class using a trait take precedence over methods declared in the trait. However, methods in a trait will override methods inherited by a class.

Expressed more simply, precedence in traits and classes is as follows:

CLASS METHODS first, then TRAIT METHODS, and lastly INHERITED METHODS.

Conflict resolution in traits

What happens if your class is using two traits and they both declare a method with the same name?  

PHP requires you to explicitly resolve these sort of conflicts and will throw a fatal error if you try to use two traits that each define a method of the same name.

There are two approaches to resolving name conflicts between traits.  You can either tell PHP not to use a method that is defined in one of the traits or you can tell PHP to use an alias for a method in one of the traits.  Put shorter, you can exclude a method or alias a method.

In order to exclude a method you use the `insteadof` operator which tells PHP to use a method from one trait instead of the method from the other.

In order to alias a method you use the `as` operator to provide an alias to use.  The only trick here is that it's not enough to just specify an alias, you must *also* exclude the original method from being used.

Right, so we've got that we can either exclude or alias a method to resolve naming conflicts between traits.  Lets look at some code to see how it works.



First we're going to create two traits.  We're going to define two methods in each trait and name them such that they'll create naming conflicts.

We're going to use the traits in a class which we're calling DomesticPet.  Because the methods in the traits have conflicting names we're going to have to resolve these conflicts.

We resolve the conflicts in a code block that follows the `use` keyword.  We have to resolve every conflict so lets step through this now.

Replacing methods with "insteadof"

For the first method, `makeNoise()`, we're going to simply exclude the method from the Dog class.  We would do this if we only ever want to use the Cat method and expect never to use the Dog method.

In order to do this we're going to tell the class to use the makeNoise function declared in the Cat trait instead of the one in the Dog trait.  

We use the `insteadof` keyword to tell PHP to use the method declared in the Cat trait instead of the one in the dog class.

Whenever the `makeNoise()` function is called PHP will know that you mean the one defined in the Cat trait and you won't be able to access the one from the Dog trait.

Aliasing methods with "as"

For the second method, `wantWalkies()`, we're going to rather alias the method in Cat.  We would do this when we want to be able to use both methods in our class.

You may have already noticed in the code sample that there are three statements in the conflict resolution block but only two functions that need to be resolved.  

Remember that earlier I said the trick with aliasing methods is that it's not enough to alias by itself and that you must also exclude the method you're aliasing?  

We're going to need two statements to alias our method.

First we're going to tell PHP to give the method an alias that we can use to reference it.  Then we're going to tell PHP to exclude the original method.

In order to alias the method we use the `as` keyword.   We're going to give the `wantWalkies()` method an alias of `kittyWalk()`.  By doing this you will be able to call the `kittyWalk()` function in your class.  This isn't a new function, it's just an alias for the function in the Cat trait.

Right after we alias the function we go ahead and tell PHP to use the Dog `wantWalkies()` method instead of the Cat trait.  If we left out this step we would still have a naming conflict.

The end result

We used the Cat `makeNoise()` function instead of the Dog one.  When we call the `makeNoise()` method on the object we are referencing the cat function and so the output is "Purr".

We aliased the Cat `wantWalkies()` method as `kittyWalk()` and so when we call the `kittyWalk()` method on the object we are referencing the aliased Cat method and the output is "No Thanks".

Because we are aliasing the Cat `wantWalkies` method we also had to use the Dog method instead of the Cat method.  Therefore when we call the `wantWalkies` method on the object we are referencing the Dog method and the output is "Yes Please".

Visibility

You might want to change the visibility of a method that you're using from a trait.  This is done by using the `as` keyword.  

Lets look at a simple example before we take a look at the complications.

We can set an aliased method as protected by inserting the keyword `protected` between `as` and the name we're aliasing it as.  For example we can say,

Dog::wantWalkies as protected doggyWalk;

Changing the visibility of a replaced method is slightly more complicated.

In order to change the visibility of a replaced method we need to use two steps.  First we use `insteadof` to replace the method and then in the next statement we stipulate that it must be protected.

Lets look at this in code.  



In the first line inside the trait use block I'm aliasing a function and also changing its visibility.  The keyword `protected` appears between `as` and the aliased name.  If I were to try and call `doggyWalk()` from outside the class I would receive an error because it is protected.

I've commented out the second line because it's an example of what doesn't work.  The rule of thumb seems to be that you can't use both `as` and `insteadof` in one single statement.  You need to split them into separate statements as I do in the last two lines of the use block.

Comments

Popular posts from this blog

Separating business logic from persistence layer in Laravel

There are several reasons to separate business logic from your persistence layer.  Perhaps the biggest advantage is that the parts of your application which are unique are not coupled to how data are persisted.  This makes the code easier to port and maintain. I'm going to use Doctrine to replace the Eloquent ORM in Laravel.  A thorough comparison of the patterns is available  here . By using Doctrine I am also hoping to mitigate the risk of a major version upgrade on the underlying framework.  It can be expected for the ORM to change between major versions of a framework and upgrading to a new release can be quite costly. Another advantage to this approach is to limit the access that objects have to the database.  Unless a developer is aware of the business rules in place on an Eloquent model there is a chance they will mistakenly ignore them by calling the ActiveRecord save method directly. I'm not implementing the repository pattern in all its glory in this demo.  

Fixing puppet "Exiting; no certificate found and waitforcert is disabled" error

While debugging and setting up Puppet I am still running the agent and master from CLI in --no-daemonize mode.  I kept getting an error on my agent - ""Exiting; no certificate found and waitforcert is disabled". The fix was quite simple and a little embarrassing.  Firstly I forgot to run my puppet master with root privileges which meant that it was unable to write incoming certificate requests to disk.  That's the embarrassing part and after I looked at my shell prompt and noticed this issue fixing it was quite simple. Firstly I got the puppet ssl path by running the command   puppet agent --configprint ssldir Then I removed that directory so that my agent no longer had any certificates or requests. On my master side I cleaned the old certificate by running  puppet cert clean --all  (this would remove all my agent certificates but for now I have just the one so its quicker than tagging it). I started my agent up with the command  puppet agent --test   whi

Redirecting non-www urls to www and http to https in Nginx web server

Image: Pixabay Although I'm currently playing with Elixir and its HTTP servers like Cowboy at the moment Nginx is still my go-to server for production PHP. If you haven't already swapped your web-server from Apache then you really should consider installing Nginx on a test server and running some stress tests on it.  I wrote about stress testing in my book on scaling PHP . Redirecting non-www traffic to www in nginx is best accomplished by using the "return" verb.  You could use a rewrite but the Nginx manual suggests that a return is better in the section on " Taxing Rewrites ". Server blocks are cheap in Nginx and I find it's simplest to have two redirects for the person who arrives on the non-secure non-canonical form of my link.  I wouldn't expect many people to reach this link because obviously every link that I create will be properly formatted so being redirected twice will only affect a small minority of people. Anyway, here's