- Normal
- |
- Widen Column
- |
- Larger Text
Recently read:
And So Bits Of Me Fall Into Place,Lets see an example of how we can use an OOP design pattern to write a simple piece of code. Nah! i won't be working on the 'Animal-Dog' or 'Shape-Rectangle' theme that is so commonly used to explain OOP concepts; instead we'll try to build a website link shortener service. Thats right, the work that tinyurl, bit.ly, is.gd, cli.gs and so many other url compression services perform so that we don't cross the sacred 140 barrier in our hourly holy updates. No, we won't build a new shortener program but rather bring all these services into a simple, usable interface that our end-user can call whenever he wants a short url of his choice.
So, before you start kneading the php dough, take a look at the brief articles here and here. Notice that both functions are pretty much the same in that they all use the cURL tool to fetch the shortened url through the shortener's api. In other words, a shortening website like bit.ly provides a piece of code (its api) to which we attach our long url. Then with the help of cURL, the long url is processed and the final product is returned as a short url. Hey! The words 'processed' and 'product' allude to a factory, don't they? So, lets start thinking in objects now. You could say,
Hmmm, I want an is.gd url. I'll send the product specifications to the central factory wherein the shortener workers along with the cURL workers will take those requirements and make a device called Acme Is.gd Mega-Shortener. I'll then put my long, unwieldy url into this shiny new contraption and, bam! Out comes the short url!
How do you translate this thought into code? Remember that as an end-user (who can be a another programmer or a normal user), you're only interested in the part that gives you the short url and not how its processed. This means that you'll never want to create concrete objects of the factory class (abstract then?). Instead, if you want an Is.gd url, you'll create concrete objects out of the Is.gd class. This class will contain your specifications to shorten the url. The factory will return you a 'new Is.Gd' object and you only have to play with that. Phew! You get the idea, right?
So, the central factory contains a method that creates the short url. The same method uses a cURL object to help it in the processing. Therefore, we'll have a CurlService object separate from a UrlShortenService object (You never know when CurlService becomes useful in some other program). A rough sketch of the corresponding classes would be:
a) CurlService: Like a company handling cURL services
class CurlService
{
/**
* Stores the current curl session when a url class is instantiated
* @var curl session
*/
protected $_curl;
function __construct()
{
if(!isset($this->_curl))
{
$this->_curl = curl_init();
}
else throw new Exception("Couldn't start curl session");
}
/**
* Starts a new curl session (if constructor is not used)
* @return void
*/
public function startSession()
{
$this->_curl = curl_init();
}
/**
* Close curl session
* @return void
*/
public function closeSession()
{
curl_close($this->_curl);
}
/**
* Set the CURLOPT_URL
* @param string $url
* @return void
*/
public function setCurlUrl($url)
{
curl_setopt($this->_curl, CURLOPT_URL, $url);
}
/**
* Execute the current curl session
* @return curl object
*/
public function execute()
{
return curl_exec($this->_curl);
}
/**
* Set the CURLOPT_RETURNTRANSFER
* @param number $v
* @return void
*/
public function setReturnTransfer($v = 1)
{
curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, $v);
}
/**
* Set the CURLOPT_CONNECTTIMEOUT
* @param number $timeout
* @return void
*/
public function setConnectTimeout($timeout = 10)
{
curl_setopt($this->_curl, CURLOPT_CONNECTTIMEOUT, $timeout);
}
// You can add other setters here that set curl options like CURLOPT_FOLLOWLOCATION, CURLOPT_HEADER etc.
}
b) UrlShortenService: Our central factory where short urls are created
class UrlShortenService
{
protected $_longurl, $_api;
function __construct() {
}
/**
* Factory method to create a short url.
* @return string Shortened url
*/
protected function factory()
{
try{
$url = $this->_api . $this->_longurl;
$curl = new CurlService();
$curl->setCurlUrl($url);
$curl->setReturnTransfer(1);
$curl->setConnectTimeout(5);
$shorturl = $curl->execute();
$curl->closeSession();
return $shorturl;
} catch(Exception $e) {
print("Could not create the short url. Curl error reported as: $curl | Exception reported as: " . $e->getMessage());
}
}
/**
* Returns the shortened url (public method)
* @return string
*/
public function getUrl()
{
return $this->factory();
}
}
You'll notice that the factory method contains the same steps as instructed in the blog articles you read earlier. So far so good. But how do we prepare the url to be shortened? We delegate that task to individual shortening services i.e. we'll create classes like Tinyurl, BitLy that'll handle that task (Now you know why i kept the variables in UrlShortenService protected?). This is pretty obvious as different websites have different apis. Its upto these individual classes to prepare the url - the product specifications - and send it to the factory to be processed. Therefore, an IsGd class will look like:
c) IsGd: like a factory department or branch that provides is.gd customizations
class IsGd extends UrlShortenService
{
function __construct($url = '')
{
/* the variables are inherited from UrlShortenService class which are defined as protected there*/
$this->_longurl = $url;
$this->_api = 'http://is.gd/api.php?longurl=';
}
}
Thats it? Yeah! And here's how the end-user will get his short url:
‹?php
// our shiny new Acme IsGd Mega-Shortener device
$acme_isgd = new IsGd("http://myverylongurlthattakesupalottaspace.html");
echo $acme_isgd->getUrl(); //prints the shorturl (with a bam!)
Easy, wasn't it? You can get, say, Tinyurl or To.ly by just creating the respective classes and adding the api in the constructor. But wait, what's that you say?
There's a service called 'foo.bar' that needs to set some extra curl options to prepare the short url. The central factory isn't equipped with this new technology feature.
If you were the factory manager, you would know that technology upgradation of the central factory is a bad idea. You provide free url shortening services and live off paypal donations. You don't have the money for a costly upgradation! Ahem, bad jokes aside, in such a case, what you can do is run the initial fabrication in a small factory branch or a department that handles foo.bar cases. Then the preliminary product can be packaged and sent to the main factory for the rest. This means that we override the factory method in a FooBar class as shown below:
d) FooBar: Our new factory branch to provide foo.bar specifications
class FooBar extends UrlShortenService
{
function __construct($url ='')
{
$this->_longurl = $url;
$this->_api = 'http://foo.bar/api.php?longurl=';
}
// notice that overriding factory also takes arguments as php does not yet support overloading
protected function factory($url = '', CurlService $curl = null)
{
$curl = new CurlService();
$curl->setHeader(0); // <-- the new feature needed for this shorturl
// package the longurl+api with the curl session and send it to the central factory
return parent::factory($this->_api . urlencode($this->_longurl), $curl);
}
}
The central factory now looks like:
e) UrlShortenService: the factory now takes arguments to check the validity of the initial product (the api+longurl)
class UrlShortenService
{
....
/**
* Factory method to create a short url.
* @param string $url
* @param CurlService $curl
* @return string Shortened url
*/
protected function factory($url = '' , CurlService $curl = null)
{
/* Checks if the url has been prepared (packaged) by the child factory or not.
* If not, create the url to be processed here
*/
if(!isset($url) || trim($url)==='')
{
$url = $this->_api . $this->_longurl;
}
try {
// The $curl variable will be empty if the factory method has not been overridden
if (!$curl) $curl = new CurlService();
// Rest of the code below is unchanged
$curl->setCurlUrl($url);
$curl->setReturnTransfer(1);
$curl->setConnectTimeout(5);
$shorturl = $curl->execute();
$curl->closeSession();
return $shorturl;
} catch(Exception $e)
{
print("Could not create the short url. Curl error reported as: $curl | Exception reported as: " . $e->getMessage());
}
}
}
To get the short url, just run the code as explained for is.gd url above.
All done and ready to roll. But before you pat yourself on the back, there's another problem:
If i want to create 10 different url shorteners at the same time, am i not running a new central factory object with every instance of the individual url-shortener i create? Thats like 10 different central factories loaded in memory!!
Blimey! You are right, there should be only one factory. When i create a new IsGd object, its calling the UrlShortenService factory that it extends. And when i instantiate Tinyurl, it calls its own UrlShortenService factory! Yikes!
The solution is simple, make the central factory a static class. The individual shortener objects can then access the lone factory method loaded in memory. So then how do i get the short url? Remember when i said in the beginning that we want to create a simple, usable interface for the end-user so that can we bring all these shortening services together? The end-user must say:
I want to get a bit.ly url from the url shortening service. I don't care how bit.ly is made; i just want the url-shortening service to get me what i want.
In the above statement, note that the url shortening service (i.e. UrlShortenService class) will provide the interface for the user to interact with. Hence, lets update the code as follows:
f) UrlShortenService: factory class is now static
class UrlShortenService
{
protected $_longurl, $_api;
// Constructor
function __construct() {
throw new Exception('UrlShortenService is a static class. No instances can be created.');
}
protected function factory($url = '' , CurlService $curl = null) {
//same as in e) above
}
public static getShortUrlFor($obj) {
/* If child shortener has an overriding factory method as discussed above,
* that factory method is called
*/
return $obj->factory();
}
}
To get the short url, all you have to do is write the following code:
‹?php
echo UrlShortenService::getShortUrlFor(new TinyUrl('http://verylongurl.html'));
I can see you are a little dazed. I'll give you a few moments to gather your thoughts and arrange them. Its the most elegant (not to mention simplest) solution we have produced among all the things we have tried here. Don't you agree?
What's that you say little yellow box?
What if each shortener website produces short urls in a different way?
In that case, you can make the parent factory method abstract, and consequently the UrlShortenService class becomes abstract as well. Then each shortener class has the responsibility to produce the short url. This is the actual factory pattern. What we discussed thus far was a modification of this pattern (i am sorry if i betrayed your expectations!)
And thats all there is to it. Pretty long, eh? Hope you've got a whiff of practical OOP in PHP. When you write your project next time, try to model your problem in terms of objects. Although, this maybe an overkill for a simple url shortener function, you can see the importance if its a bigger, more complex system.
Before we end, as a simple exercise, how will you create a shorl.com short url? Its api code is "http://shorl.com/create.php?url="
Click here for answer ↓
Shorl class:
class Shorl extends UrlShortenService
{
function __construct($url ='')
{
$this->_longurl = $url;
$this->_api = 'http://shorl.com/create.php?url=';
}
}
Usage:
‹?php
echo UrlShortenService::getShortUrlFor(new Shorl('http://myverylongwebsitelink.html'));
Filed under: Tutorials
How about some bookmarking love?
Post To Twitter Bookmark on Delicious Bookmark on Digg Share on Facebook Update On Friendfeed Bookmark on Reddit Bookmark on Mixx