CHAPTER 12

Vendors

One of the benefits of using Cake is that it is built on PHP, one of the most popular languages in web development. Thousands of developers from across the globe actively contribute to the available body of PHP classes and scripts, which makes using PHP advantageous. However, a major concern of PHP developers choosing a framework is how to port over existing projects or use those long hours of developing their own classes in the framework.

PHP uses the include() and require() functions to reference external scripts or multiple files. Why not just run one of these to implement an outside script? Again, Cake's "convention over configuration" mantra applies. Cake is concerned not just with providing shortcuts and automating typical web functions but also with ways of naming and arranging the various application resources. Rather than arbitrary includes, Cake suggests that any third-party script or non-Cake objects be organized as vendors.

Using Vendors

Simply put, a vendor is a non-Cake script of some kind. For example, let's say you want to include a snazzy PHP class script you found on the Internet that simplifies generating CAPTCHA1 images. You can assume that the developer was not thinking of CakePHP specifically when building the script, so its functions and classes won't be structured to work in Cake. No problem—you just have to bring the script into the application as a vendor.

The first step is to place the PHP script into the app/vendors folder. By doing so, you make the script available to the Cake application while keeping all the vendors in one place. You won't need to rename the file to fit Cake's conventions.

Next, you include the file's contents in the controller, model, or view where you'd like to use the script. You do this by using the App::import() function (see Listing 12-1).

Listing 12-1. Including a Vendor

<?php
App::import('Vendor','some_captcha_script.php'),

______________________

1. "Completely Automated Public Turing test to tell Computers and Humans Apart"; in other words, by supplying images that are difficult for a computer to decipher but easy for a human to read, the web site can differentiate between automated requests and legitimate user form submissions.

Notice that in Listing 12-1 the first parameter is Vendor; this tells Cake to import a vendor file. Also, I've included the standard PHP opening line, <?php, to emphasize where this function is called: before any class declarations. So if Listing 12-1 occurred in the Posts controller, the App::import() function would appear before the class declarations:

<?php
App::import('Vendor','some_captcha_script.php'),
class PostsController extends AppController { ...

Finally, you can use the vendor's contents in the controller or view by simply calling its functions or classes as if it were brought in with the include() function (see Listing 12-2).

Listing 12-2. An Example Vendor Function Being Used in the Controller

1    <?php
2    App::import('Vendor','some_captcha_script.php'),
3    class PostsController extends AppController {
4        function index() {
5            $captcha = new someCaptchaClass();
6            $captcha_image = $captcha->generateImage();
7            $this->set(compact('captcha','captcha_image'));
8        }
9    }
10    ?>

Remember that Listing 12-2 is a basic example that would require more work to be done in the view to work properly. But you can see how the vendor is imported on line 2 and how its class object is brought into the controller on lines 5–6.

Using vendor functions without class objects is just as simple. Let's assume the contents of some_captcha_script.php include just one function:

function cleanAmpersands($text=null) {
    return str_replace('&','&amp;',$text);
}

Then, in the action, simply use the function as in a normal PHP script:

function view($id=null) {
    $post = $this->Post->read(null,$id);
    $text = cleanAmpersands($post['Post']['contents']);
    $this->set(compact('post','text'));
}

Referencing outside scripts through the vendor system can be extremely useful, especially when you want to use available scripts that are not constructed as Cake functions. Using the App::import() function makes this a simple task, keeps vendors organized, and manages the paths to the vendor files.

Dealing with File Names

The App::import() function is used for more than just including vendors. Other Cake core class objects can be brought into a controller or view with this function as well. Because of this, App::import() performs some default methods that affect how you specify a vendor file for inclusion in the application. Cake will automatically include vendor files based on the name as long as the specified name is all lowercase and matches the file name exactly. This means you must specify the vendor by using all lowercase letters. If a file name is uppercased, title-cased, camel-cased, or anything other than all lowercase, the vendor will not be auto-matically included, and more parameters will need to be provided.

App::import('Vendor','Title'), //will not bring in the file Title.php

If the vendor file is in a folder or has any uppercase letters, then you must manually specify the file name using a parameters array. To bring Title.php into the controller, you would have to pass along the file name in the parameters array or rename the file itself:

App::import('Vendor','title',array('file'=>'Title.php'));

Dealing with Nested Folders

Suppose the vendor you're importing into the application has an internal folder structure that needs to be preserved if it is to work properly. The App::import() function can also take care of vendors in these folders so long as the path supplied is correctly formatted. Remember that all paths will be relative to the app/vendors directory.

App::import('Vendor','title',array('file'=>'my_vendors/title.php'));

The previous line will include the app/vendors/my_vendors/title.php file for use in the application.

Making No Assumptions for Third-Party Scripts

An important point about vendors must be made: Cake makes no assumptions about vendors. Doing so would contradict the whole purpose of having a vendor in a Cake application in the first place. In other words, the main reason you might use a vendor in a Cake application is to take advantage of a feature, method, or task that Cake cannot already handle by itself. So if you pull a third-party script into Cake that performs methods Cake doesn't understand, then you must accept that Cake cannot help if anything goes wrong.

Simply put, including vendors is a mechanism to give you the flexibility to pull outside resources into your application without being limited by the framework; but Cake, as a framework, cannot assume to manage the limitations of the vendor. Therefore, the number of functions and methods provided by Cake to support vendors really goes only so far as including them in the application. From there, getting the vendor to work will require understanding the specific construction of the vendor itself and how it will react with Cake.

Furthermore, naming conventions, patterns, and the MVC structure cannot be expected to extend into vendors. If you needed to perform controller logic, you ought to write the code in a controller or component, not in a vendor. I once spoke with a beginning Cake developer who tried to get around learning Cake's functions and structure by writing everything as vendor files and pulling in only one or two Cake resources. This not only defeated the purpose of using Cake in the first place but also resulted in more time being wasted debugging vendor files.

Should you want to distribute a resource you built in Cake, you'll probably want to keep it the way it is and not release it as a vendor. In other words, if what you have written is a helper class, then make it available to other developers as such; they will better understand what you have in mind with the script, and more important, they will know how to use it in their applications. Vendor includes are best for libraries or other scripts that were developed independently of CakePHP for use in a wide variety of applications.

Unidirectional Scripting

Another important point about vendors is that Cake supports them unidirectionally. The vendor file itself cannot use Cake helper functions, class objects, or other resources. Trying to pull, say, the Ajax helper into a vendor would be more problematic and would require a lot of retooling to pull it off. The problems associated with this are apparent; forcing the vendor to change its behavior cancels out the utility of using Cake. A lot can be said for vendors, simply because so often beginners to Cake think in terms of include files and not the other way around. So, remember—Cake can use the functionality of a vendor, but the vendor cannot use the functionality of Cake. Vendor data flows in one direction, not back and forth.

Installing a Third-Party Script

Now that I've covered the basics of installing a vendor, let's use a third-party script in your blog application. Rather than build a wiki engine from scratch, let's choose one of the many open source text-parsing engines: Textile.

First, obtain a copy of Textile, which is available at http://textile.thresholdstate.com. In this tutorial, I'll be using version 2.0. Once you have downloaded the file and decompressed it, you'll find the main engine file named classTextile.php. Place this file in the app/vendors folder for the blog application.

Including Textile

Make the classTextile.php file contents available to the Posts controller by using the App::import() function for including vendors (see Listing 12-3). Be sure to place this line before the Posts controller class instantiation line. Since the file name is not all lowercase, you must specify the file name in the parameters array.

Listing 12-3. Including Textile As a Vendor

<?
App::import('Vendor','textile',array('file'=>'classTextile.php'));
class PostsController extends AppController { ...

Now that Textile is included in the Posts controller, the view can use its functions to parse the post content. Vendors included in the controller will be available in the view, so you need to add nothing else in the controller. Now, because everything will depend on the vendor itself and because Cake cannot provide conventions for third-party scripts, this process may need to change depending on what vendor is being used. Textile itself comes into play when any kind of text needs to be rendered in the browser following a wiki-style parser. Thus, this vendor in practice would be executed in the view as the view renders the contents provided by the database.

Instantiating and Running Textile

In the app/views/posts/view.ctp file, start a new instance of the Textile class by inserting Listing 12-4 into line 1.

Listing 12-4. Starting a New Textile Class

<? $textile = new Textile();?>

Thanks to the Posts controller and Listing 12-4, the class object functions in classTextile.php are now available for use in the view. Textile has the TextileThis() function to filter the final text through its engine and parse out its own unique wiki-style commands. Line 6 now contains the post's article content as passed in the $post['Post']['content'] variable. Let's run this variable through Textile's TextileThis() function by substituting the following line for line 6:

<?=$textile->TextileThis($post['Post']['content']);?>

Writing Posts with Textile

Now that Textile is running in your application, you can use its syntax when writing blog posts to easily add some text-formatting touches. Since Textile works on final output, you don't have to rework anything in the other Add or Edit actions; simply adhere to the syntax when entering text, and all should appear correctly.

To test your implementation of Textile, try adding some content in a blog post using some of Textile's wiki commands. For example, placing underscores around a word will result in the <em> tag being wrapped around the word, and so forth.

Using Other Frameworks with CakePHP

Perhaps one of the most complicated aspects of using a PHP framework is working it into a large enterprise-level application that runs on multiple frameworks. You may be familiar with some of the other web frameworks out there; you may even have experimented with a few of them before trying Cake. The question of how to implement another framework with Cake is sometimes the most important concern of all.

Because you can't assume that these frameworks are entirely compatible with Cake, conventions for using them have not been arranged or included in the core. To bring them into the application, you must tackle the project on an individual basis; each framework will have very different methods of working by itself and will certainly require a unique set of parameters and methods to work with Cake.

Zend Framework

One of the most popular PHP frameworks is Zend Framework (http://framework.zend.com). You may need to take advantage of its functions for your web applications. To do so, you must use Zend Framework (Zend, or ZF for short) as a vendor in the Cake application.

Fortunately for Cake developers, Zend is less of an MVC framework than a set of libraries (or as they are often referred by Zend developers, components). Each of its components can be brought into a stand-alone PHP application through includes. As already noted, using these components as vendors allows you to integrate Zend with your Cake application. Each component will have its own specific installation procedures for which you will need to consult Zend's support web site and documentation. But the general steps when installing a Zend component in Cake are explained in the following sections. First, let's see whether you can use anything that Zend has to offer in your blog application.

Blocking Spam in the Comments Add Action

One of the competitive features of Zend is its web service library. As of version 1.5, Zend comes with these web services components:

  • Akismet
  • Amazon
  • Audioscrobbler (Last.fm)
  • Del.icio.us
  • Flickr
  • Nirvanix
  • Simpy
  • SlideShare
  • StrikeIron
  • Technorati
  • Yahoo!

You could take your blog in any number of directions, from doing automatic title checking in Amazon to fetching images from Flickr. However, the most immediate and probably the most important basic feature of the blog is spam checking. Since the blog provides an arena where users can enter their own comments, you have opened up the possibility of your blog being spammed, especially if the blog becomes well known. Thanks to Zend, the Akismet web service component is already available, and all the hard work has already been done for you. The trick is just getting it to work in your Cake application.

After doing some homework on what Zend is offering with its Akismet component, you will find that you will need an API key (common for using any type of web service), which is provided by Akismet (%www.akismet.com). Be sure to obtain this API key; otherwise, the Akismet component will not run properly. (More information on the Akismet component is available at http://framework.zend.com/manual/en/zend.service.akismet.html.)

Download the Zend Framework library, and extract its contents. You should find a folder named Zend that contains all the components for the framework. If you already are running Zend on your server, then take note of the path relative to the app/vendors folder. Otherwise, place the Zend folder in the app/vendors directory. As with the Textile installation, write the App::import() function in the Comments Add action to include the Zend Akismet component (Listing 12-5).

Listing 12-5. Importing the Akismet Component in the app/controllers/comments_controller.php File

5    function add() {
6        App::import('Vendor','akismet',array('file'=>'Zend/Service/Akismet.php'));

At this point, you will need to deviate from the typical Cake protocol because you are now dealing with Zend. In line 6 of Listing 12-5, you brought in the Akismet component by referencing the Zend/Service/Akismet.php file. But there is one problem with this script when it is called in the Comments controller: it runs its own includes relative to the Zend directory. This will need to change to keep it from conflicting with Cake's paths. Open the Zend/Service/Akismet.php file, and insert a new include_path definition before any include functions to fix this:

22    ini_set('include_path', dirname(dirname(dirname(__FILE__))));

Now you can begin using the Akismet component in the controller. Next, instantiate a new object by calling the Zend_Service_Akismet class in the Comments Add action:

7    $akismet = new Zend_Service_Akismet('************','http://localhost/blog'),

Notice that to begin a new Akismet object, you must supply the API key in the first parameter and the URI to the blog in the second. The key, which you should have obtained from Akismet, is usually a 12-digit alphanumeric string.

Because you changed the INI settings to make the include_path parameter work for Zend, you will need to go back and override this exception. After line 7, insert the original include_path definition (which is stored in the app/webroot/index.php file):

8    ini_set('include_path',CAKE_CORE_INCLUDE_PATH.PATH_SEPARATOR.ROOT.DS.image APP_DIR.DS.PATH_SEPARATOR.ini_get('include_path'));

For Akismet to run a spam check, it will need a few important variables:

  • The user's IP address
  • The user agent
  • The type of comment (for example, blog or contact)
  • The comment's author name
  • The comment's content

Other variables may be passed along as well, including the author's e-mail, the author's web site URL, the permalink location, and so forth. The more variables you can pass to Akismet, the more effective it will be at filtering spam. Let's specify these variables by assigning them to the $this->data array (see Listing 12-6). Then, run the Akismet component's isSpam() function to perform the spam check.

Listing 12-6. The Rest of the Akismet Component in the Comments Add Action

9    if (!empty($this->data)) {
10            $this->data['akismet'] = array(
11                'user_ip' => $_SERVER['REMOTE_ADDR'],
12                'user_agent' => $_SERVER['HTTP_USER_AGENT'],
13                'comment_type' => 'blog',
14                'comment_author' => $this->data['Comment']['name'],
15                'comment_content' => $this->data['Comment']['content']
16            );
17
18            if ($akismet->isSpam($this->data['akismet'])) {
19                $this->render('add_spam','ajax'),
20                exit();
21            }

Lines 10–16 contain the keys and values to properly format the array to be sent to the Akismet component for processing. Notice that the keys adhere to the documented parameters listed on Zend's Akismet web page. Then, on line 18, you pass the whole $this->data['akismet'] array to the $akismet->isSpam() function and check for a true return value. Line 19, which is called if Akismet determines that the submission is spam, renders a new view in the comments folder (app/views/posts/add_spam.ctp). Line 20 exits the controller.

That's all! Now you can test the spam check by pasting in some obvious spam messages, and it should return the add_spam.ctp view. Rather than building your own Akismet component for Cake, you can take advantage of Zend's Akismet library, which combines the best of both worlds: Cake's MVC and mature framework structure and Zend's powerful web services components.


Tip Depending on your server setup, you may encounter some problems with getting the Zend component to instantiate the class properly. A common error is the "cannot access protected property" error, which is often because of a PHP caching engine or accelerator being turned on. Be sure to check the details of your setup before turning off the caching engine or PHP accelerator, but a good way to test this problem is by toggling this setting from on to off. Other issues could be because of conflicts with Zend; check Zend's recommendations to resolve the bug.


Outputting Posts as PDF Files

Zend also comes with a powerful PDF component that would be useful for your blog application. Let's create an action that will output the contents of the post as a PDF.

In the Posts controller, insert the action shown in Listing 12-7.

Listing 12-7. The PDF Action in the Posts Controller

1    function pdf($id = null) {
2        if (!$id) {
3            $this->redirect(array('action'=>'index'),null,true);
4        }
5
6        $text = $this->Post->read(null,$id);
7
8        App::import('Vendor','pdf',array('file'=>'Zend/Pdf.php'));
9        $pdf =& new Zend_Pdf();
10        ini_set('include_path', CAKE_CORE_INCLUDE_PATH.PATH_SEPARATOR.ROOT.DS.image
APP_DIR.DS.PATH_SEPARATOR.ini_get('include_path'));
11
12        $page = $pdf->newPage(Zend_Pdf_Page::SIZE_LETTER);
13        $pdf->pages[] = $page;
14        $page->setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_image
HELVETICA), 12);
15        $text = wordwrap($text['Post']['content'], 80, " ", false);
16        $token = strtok($text, " ");
17        $y = 740;
18        while ($token != false) {
19            if ($y < 60) {
20                $pdf->pages[] = ($page = $pdf->newPage(Zend_Pdf_Page::SIZE_image
LETTER));
21                $y = 740;
22            } else {
23                $page->drawText($token, 40, $y);
24                $y-=15;
25            }
26            $token = strtok(" ");
27        }
28
29        $data = $pdf->render();
30        header("Content-Disposition: inline; file name=blog_post.pdf");
31        header("Content-type: application/x-pdf");
32        echo $data;
33        exit();
34    }

Notice how Listing 12-7 uses the PDF component. On line 8, the component is imported as a vendor, and then line 9 instantiates a new PDF class object. Line 10 reverts the include path setting back to the default Cake path (since we will soon change the include path to work in Zend). Lines 12–33 follow the generic pattern for creating PDF files with the PDF component.

For more detailed help on using the PDF component, consult the online documentation (http://framework.zend.com/manual/en/zend.pdf.html). In short, you create a new PDF file and write new pages to the file. Since lines of text are "drawn" rather than "typed" in a PDF file, you must manually wrap the lines. This is accomplished on lines 15–27. Lines 29–32 send the formatted data to the browser with PDF-specific HTTP headings, and then line 33 exits to prevent Cake from rendering any views.

The script cannot work yet unless (as with the Akismet component) you alter the include_path setting for Zend to work correctly. Open the app/vendors/Zend/Pdf.php file, and place the following line near the top and before any includes:

ini_set('include_path', dirname(dirname(__FILE__)));

You're all set. Launch the PDF action, and a basic PDF file should automatically render. You may want to add a link in the Posts view to output as a PDF file:

<?=$html->link('Save as PDF','/posts/pdf/'.$post['Post']['id']);?>

Other Zend Components

Many other powerful components are included in the Zend Framework. Aside from some components that make Zend work in an MVC environment, you ought to be able to bring various components into a Cake application. In this exercise, try your hand with a couple of unique components that aren't already available in Cake, such as Lucene, Translate, or Validate, by bringing them into a Cake application as vendors. Remember to fix the include paths for Zend to work properly and test the components thoroughly since they are third-party elements.


Summary

When you must use third-party scripts in your Cake application, it's best to include them as vendors using the App::import() method. Once the script is available, its functions and objects can be directly called in the application. Of course, because vendors work in Cake unidirectionally, how you further implement the vendor will depend on the vendor's own methods. In this chapter, we discussed using components from Zend Framework to perform Akismet spam checks and PDF file rendering. Other web service components can be used in Cake using similar methods. In Chapter 13, we'll examine plugins—resources that allow you to create a mini-application of sorts that can be distributed across multiple Cake applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.131.213