Working as a CodeIgniter developer, you might have come across the concept of a library that enriches the core framework functionality, and CodeIgniter itself provides lots of useful libraries in the core.
Similarly, a driver is a special kind of library that allows you to add custom features so that the main driver class acts as a parent class and the adapters are treated as child classes.
The best way to understand the concept of drivers is to look at how caching is implemented in the core CodeIgniter framework. The main Cache class acts as a parent class and extends the CI_Driver_Library class. On the other hand, you'll end up finding child classes for APC, Memcached, Redis and the like, implemented as pluggable adapters. The child classes extend the CI_Driver class instead of the main driver class.
The beauty of this approach is that you could easily extend the driver's functionality by adding a new adapter as needed. As in the case of caching, if you need to add a custom caching strategy, you're just a step away from implementing it in the form of a custom adapter.
Creating a custom driver in the CodeIgniter application is the aim of today's article. In the course of that, we'll go through a real-world example that creates a MediaRenderer driver used to render the media from different services like YouTube, Vimeo and similar. The different services will be implemented in the form of adapter classes.
File Setup
The name of the driver that we're going to implement in this article is MediaRenderer. Let's have a quick look at the list of files that are required for the desired setup:
application/libraries/MediaRenderer/MediaRendererInterface.php
: It's the interface that the adapter needs to implement.application/config/mediarenderer.php
: The configuration file that holds our custom driver related settings.application/libraries/MediaRenderer/MediaRenderer.php
: It's the class that extends the CI_Driver_Library and is used to operate the different adapters available in the application.application/libraries/MediaRenderer/drivers/MediaRenderer_youtube.php
: It's the class that implements the YouTube adapter.application/libraries/MediaRenderer/drivers/MediaRenderer_vimeo.php
: It's the class that implements the Vimeo adapter.application/controllers/Media.php
: It's the controller class that we'll implement to demonstrate the usage of our custom driver.
So that's the list of files we're going to implement throughout this article.
Create Drivers
In this section, we'll create the base files of our custom driver.
The first thing that we need to do is to define the configuration file of our custom driver. Let's define the application/config/mediarenderer.php
file as shown below.
<?php $config['media_services'] = array('youtube', 'vimeo'); $config['media_default'] = 'youtube';
It indicates that we're going to implement two adapters—YouTube and Vimeo. The default adapter is set to YouTube.
Go ahead and create a file application/libraries/MediaRenderer/MediaRendererInterface.php
with the following contents.
<?php defined('BASEPATH') OR exit('No direct script access allowed'); /** * MediaRendererInterface */ interface MediaRendererInterface { public function display($id); }
As you can see, it's a pretty basic interface that makes sure that the adapter that implements this interface must implement the display method.
Next, let's have a look at the MediaRenderer
driver file. Go ahead and create a file application/libraries/MediaRenderer/MediaRenderer.php
with the following contents.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); interface_exists('MediaRendererInterface', FALSE) OR require_once(APPPATH.'/libraries/MediaRenderer/MediaRendererInterface.php'); /** * MediaRenderer Class */ class MediaRenderer extends CI_Driver_Library { public $valid_drivers; public $CI; protected $_adapter = 'youtube'; /** * Class constructor */ public function __construct() { $this->CI =& get_instance(); $this->CI->config->load('mediarenderer'); $this->valid_drivers = $this->CI->config->item('media_services'); $this->_adapter = $this->CI->config->item('media_default'); } /** * Overrided __get method to check if the adapter implements MediaRendererInterface interface * @see CI_Driver_Library::__get() */ public function __get($child) { if (in_array($child, $this->valid_drivers)) { $object = $this->load_driver($child); if ($object instanceof MediaRendererInterface) { return $object; } else { show_error("MediaRenderer: Adapter '".$child."' doesn't implement MediaRendererInterface. Aborting."); return; } } else { show_error('Unable to load the requested adapter: '.$child); return; } } /** * @param string $adapter Adapter name * @return MediaRenderer */ public function setAdapter($adapter) { $this->_adapter = $adapter; return $this; } /** * @param string $id Media ID */ public function display($id) { return $this->{$this->_adapter}->display($id); } }
At the beginning of the file, we include the MediaRendererInterface
interface that we've already defined earlier in this section.
As per the CodeIgniter driver standards, our class MediaRenderer
extends the CI_Driver_Library
class that makes sure that we could easily access driver adapters using the load_driver
method defined in the CI_Driver_Library
class.
Next, let's have a close look at the constructor.
public function __construct() { $this->CI =& get_instance(); $this->CI->config->load('mediarenderer'); $this->valid_drivers = $this->CI->config->item('media_services'); $this->_adapter = $this->CI->config->item('media_default'); }
Recall the mediarenderer configuration file that we defined earlier and that's exactly loaded in the constructor in the first place. It's required that you set the list of adapters that are supported by the driver to the valid_drivers
property, so we just did that as well. And finally, we've set the value of the default driver to the _adapter
property for our convenience.
Moving further, let's pull in the code of the __get
method.
public function __get($child) { if (in_array($child, $this->valid_drivers)) { $object = $this->load_driver($child); if ($object instanceof MediaRendererInterface) { return $object; } else { show_error("MediaRenderer: Adapter '".$child."' doesn't implement MediaRendererInterface. Aborting."); return; } } else { show_error('Unable to load the requested adapter: '.$child); return; } }
I would say that you're not required to override this method, and our driver would work just fine without it as well. The reason behind the implementation of this method in our case is to enforce the implementation of the MediaRendererInterface
interface, and thus it'll make sure that each driver must implement the display method.
Next, let's take a look at the setAdapter
method.
public function setAdapter($adapter) { $this->_adapter = $adapter; return $this; }
As you can see, it's just used to override the default adapter settings in case you want to use a different adapter for the time being.
Finally, there's a display
method that calls the display method of the corresponding adapter.
public function display($id) { return $this->{$this->_adapter}->display($id); }
Again, I would say that you can skip the implementation of the display method as you could always call the display method of the adapter directly, as we'll see later in this article. However, I would like to access the adapters via the display method of the MediaRenderer
class as this is the place where you could refactor the common code the adapter may implement.
So that was the MediaRenderer
class at your disposal.
Create Adapters
We've been discussing the driver adapters for a while, and now it's time to actually implement them.
Let's start with the YouTube adapter file at application/libraries/MediaRenderer/drivers/MediaRenderer_youtube.php
.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); /** * MediaRenderer_youtube Class */ class MediaRenderer_youtube extends CI_Driver implements MediaRendererInterface { /** * @param string $id Media ID * @see MediaRendererInterface::display() */ public function display($id) { if ($id) { return '<iframe width="420" height="315" src="//www.youtube.com/embed/'.$id.'" frameborder="0" allowfullscreen></iframe>'; } } }
It's important to note that the adapter name is prefixed with MediaRenderer_
, and it also extends the CI_Driver
class. Also, it implements the MediaRendererInterface
interface to make sure that we adhere to the standards we discussed earlier.
The reason our adapter class extends the CI_Driver
class is to avail all the parent methods and properties. You've heard it right, you can access methods and properties of the MediaRenderer
class from within the MediaRenderer_youtube
class even though it doesn't extend the MediaRenderer
class directly.
Apart from that, it's fairly easy to understand the implementation of the display
method, which returns the embed code provided that the media ID is passed as an argument of the method.
The Vimeo adapter class is pretty identical to that of YouTube. Go ahead and create one at application/libraries/MediaRenderer/drivers/MediaRenderer_vimeo.php
.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); /** * MediaRenderer_vimeo Class */ class MediaRenderer_vimeo extends CI_Driver implements MediaRendererInterface { /** * @param string $id Media ID * @see MediaRendererInterface::display() */ public function display($id) { if ($id) { return '<iframe width="420" height="247" src="//player.vimeo.com/video/'.$id.'"></iframe>'; } } }
And that ends the discussion of adapters.
Putting It All Together
In the last couple of sections, we've discussed the driver and adapter classes. In this section, which is the last one in this article, we'll extend our journey to go through a demonstration of basic driver usage.
Let's start by creating a controller file application/controllers/Media.php
.
<?php defined('BASEPATH') OR exit('No direct script access allowed'); /** * Media Controller Class */ class Media extends CI_Controller { public function index() { // this will use default adapter as per the config file $this->load->driver('mediaRenderer'); // IMP: it must be a lowercase drivername when you use it echo $this->mediarenderer->display("0GfCP5CWHO0"); // override adapter settings by setting it explicitly echo $this->mediarenderer->setAdapter('vimeo')->display("225434434"); // access the adapter directly echo $this->mediarenderer->vimeo->display("225434434"); echo $this->mediarenderer->youtube->display("0GfCP5CWHO0"); } }
The first thing that we need to do is to load our custom driver mediaRenderer
, and that's what the following snippet does.
$this->load->driver('mediaRenderer');
To access the custom driver that we've just loaded, you should use the $this->mediarenderer
syntax. It's important to note the name of the driver is in lower case, irrespective of the actual driver name.
Next, let's examine what the following code does.
echo $this->mediarenderer->display("0GfCP5CWHO0");
It calls the display
method of the MediaRenderer
class in the first place, in that it delegates the control to the display
method of the corresponding adapter that's set as a default adapter in the mediarenderer configuration file. Eventually, it ends up calling the display
method of the YouTube adapter as that's the default adapter in our case.
On the other hand, if you want to call the display method of any specific adapter, you could always do that explicitly, as shown in the following snippet.
echo $this->mediarenderer->setAdapter('vimeo')->display("225434434");
As I mentioned earlier, you could also call the display method of any specific adapter directly, without going through the display
method of the MediaRenderer
class.
echo $this->mediarenderer->vimeo->display("225434434"); echo $this->mediarenderer->youtube->display("0GfCP5CWHO0");
So that's how you're supposed to call the driver and its adapters alongside—thanks to the driver structure that allows you to plug in the new adapters on-the-fly as needed.
And that's pretty much it. I hope that you've enjoyed the article, and you could always hit the comment section to express your thoughts and concerns.
Conclusion
A journey through the drivers in the CodeIgniter framework was the recipe of today's article.
As always, the article started with a basic introduction to the concept of drivers in the CodeIgniter framework. As I promised, we went on to create a custom driver based on a real-world use case, and that I believe is the best way to understand a new concept.
I would love to hear if you could come up with something about this exciting concept!
No comments:
Post a Comment