1. Trang chủ
  2. » Công Nghệ Thông Tin

PHP in Action phần 9 potx

55 256 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 55
Dung lượng 837,72 KB

Nội dung

DESIGNING A SOLUTION USING HTML_QUICKFORM 415 • Feature-revealing code. To make the code as readable as possible, let’s try to avoid making it too convenient. Hiding as much of the mechanics of the process as possible is tempting, but the code will be more self-documenting if important steps in the process are explicit—validating the form, populating it, and passing values into the template. • Te s t a b l e . A by-product of making the code more explicit is making it testable. We want to be able to replace important objects with mock objects. Typically, this involves passing them into a constructor or other method. 18.1.2 Putting generated elements into the HTML form Perhaps the greatest challenge of all in form handling is getting the form contents and the HTML markup to work together as a team. This is, of course, the same challenge as in other dynamic web pages, but a typical form has a particularly high density of individual values to be inserted, especially if there is client-side validation. Also, the form speaks to a mixed audience, since some of it is intended for the human user and some is not. We’ll see what we can do using the simplest possible strategy, and con- sider whether we need the HTML_QuickForm Renderer class. Manual template building The simplest way to insert elements of the form object into the template is the direct route. That means something like this, using PHPTAL syntax: <span tal:content="form/elements/headline/getLabel"> Headline (this is sample text that PHPTAL removes) </span> <input type="text" name="${form/elements/headline/getName}" value="${form/elements/headline/getValue}" /> We can avoid making the paths this long if we want; the example is just intended to illustrate the principle. Having a template, getting the values from the form object, and inserting them into the template is the basic way to do it. The alternative is passing the form object to the template. Do we need the renderer? Given the popularity of HTML_QuickForm, it’s not surprising that we’re not the only ones who have wanted to use it with a template engine. The developers of HTML_QuickForm have responded rather appropriately by letting a separate Ren- derer object do the job of generating the HTML for the form. That means we can produce something else by using a different Renderer. There are already other Ren- derers, including one that is specialized for Smarty. So if we want some other template engine, one thing we could do is to make our own Renderer. But is it necessary? The Renderer is an abstraction that comes out of a more 416 CHAPTER 18 FORM HANDLING complex and ambitious design than we’re after right now—one that’s capable of gener- ating the full HTML code for a form. We can achieve the same thing more simply by calling the components of the form object directly in the template, as shown earlier. We may or may not want to include the label in the form object. When the approach is this simple, it’s easy to make the label a fixed string in the template if we want the template designer to be able to change it. 18.1.3 Finding abstractions As mentioned earlier, we can get more mileage out of our code by identifying some appropriate abstractions. When dealing with forms, it’s easy to make the abstractions too dependent on the technical imple- mentation in HTML. A better way to define the abstractions is by starting out with how different input controls work. That will help decide what we need to do with them in code. And since these are user inter- face elements, “how they work” is mostly synonymous with what the user is able to do with them. This does not map exactly to the HTML implementation. A plain <select> drop-down menu does the same thing for the user as a set of radio buttons: it allows him or her to select one out of a fixed set of alternatives, as shown in figure 18.1. 1 A <select multiple> box does the same thing as a set of check boxes, allow- ing the user to choose more than one item (see figure 18.2). Table 18.1 summarizes these two abstractions and text input as well. Now that we have the abstractions and the basic guidelines, we’re ready to get more specific about what we need and don’t. 1 The W3C XForms recommendation recognizes this similarity. Figure 18.1 A plain drop- down menu is functionally equivalent to a set of radio buttons Figure 18.2 A multi-select box is functionally equivalent to a set of checkboxes DESIGNING A SOLUTION USING HTML_QUICKFORM 417 18.1.4 More specific requirements Since we’re using an existing package, we want to use the parts of it that we need, and only those. There are some features we don’t need and some we do need. We don’t need this If we use HTML_QuickForm in the default way—generating all the HTML piece by piece from the form element objects—we have to add all form controls to the form. With a template approach, this is not necessary. Even though generating everything may seem convenient, there are some things that become easier and more straightfor- ward when we use templates. All the elements that are not actually used to input val- ues can just be written as HTML in the template. This includes buttons, images, “hidden inputs,” 2 and anything else we need except text and selection input controls. In fact, if we were to decide to populate the form manually with values from the HTTP request, we would only need to represent the ones that have to be validated. We will have support for text inputs, select menus, and radio buttons. We won’t deal with multi-select controls. This leaves our form handling incomplete, but even moderately complex web applications sometimes don’t have multi-select controls, and our simple approach doesn’t burn any bridges or close any doors. We can always do it the old-fashioned way if we need to. We do need this If we want to support only the most basic elements of a form, the element objects we will need are the following: • A text input object. We don’t need to distinguish between single-line text input controls and text areas at all in our PHP code, although we will, of course, do so in the HTML markup. Both can be validated in the same way and have their values set in the same way. • A select menu object. Table 18.1 An abstract, user-oriented way of looking at form input controls User task Examples HTML implementation Text input Single-line text input field Multi-line text area <input type="text" > <textarea> Select one option from a fixed list of alternatives Drop-down menu Set of radio buttons <select> repeated <input type="radio" > Select one or more options from a fixed list of alternatives Multi-select box Set of check boxes <select multiple="multiple"> repeated <input type="checkbox" > 2 Again, thinking abstractly, it’s not really an input control even though it’s represented as one (<input type="hidden" > ) in HTML. 418 CHAPTER 18 FORM HANDLING • A radio button group object. This should work as similarly to the select menu object as possible. Usability considerations should dictate the choice between radio buttons and a menu. To the PHP application, they will look the same. Although we don’t want to generate all HTML code piecemeal, there is something to be said for generating some elements. For example, a <select> element with a dynamic number of options doesn’t resemble pure HTML much in a template, what- ever we do with it. 18.1.5 The select problem It would be easier if we had a way to set the <select> element’s value similar to the value attributes of the <input> element. There is no value attribute on a <select> ele- ment, so we can’t do this: 3 <select value="Warm"> <option>Cold</option> <option>Warm</option> <option>Hot</option> </select> Except, actually we can do it, or something similar. One way is to use some extra JavaScript. But is it really worth the possible com- plications of using JavaScript just to make the template more readable? Another is to post-process the template, replacing the previous example with this: <select> <option>Cold</option> <option selected="selected">Warm</option> <option>Hot</option> </select> But post-processing is also extra work and complexity, and we are trying hard to keep it simple. So we’re back to adding a conditional selected attribute to each and every menu <option> element. The alternative is to generate the entire select element. It’s not a major liability. The only thing the template designer loses is the ability to style the individual select ele- ment differently from the others. And generating the element is not hard to do; in fact, we can use the existing capability in HTML_QuickForm. Let’s have both options: generate the entire element or just variables so all the HTML code can be in the template. Now it turns out that we need the Renderer after all, to generate HTML for the sin- gle elements. Listing 18.1 shows how this works. 3 The XForms recommendation solves the problem by keeping all the existing values—for all form con- trols—in a separate model section instead of having value attributes on the controls themselves. IMPLEMENTING THE SOLUTION 419 require_once 'HTML/QuickForm.php'; require_once 'HTML/QuickForm/Renderer/Default.php'; require_once 'HTML/QuickForm/text.php'; $element = new HTML_QuickForm_text('headline'); $renderer = new HTML_QuickForm_Renderer_Default; $renderer->setElementTemplate("{element}"); $element->accept($renderer); print $renderer->toHtml(); All of the files—the main QuickForm file, Renderer, and text input element—have to be included separately. QuickForm elements can be created and used independently of an actual form. That is what we do in this example. We use the default Renderer; the one that gener- ates HTML. When we use the form object itself, we don’t need to think about the Ren- derer, since the form object will create it for us to generate HTML. But with an element, we have to create the Renderer. By default, the Renderer generates HTML table row and cell tags around the ele- ment. We want to prevent that, since we want to mix the element freely with other HTML markup. Fortunately, HTML_QuickForm has the ability to set a template for a single element. For our purposes, we reduce the template to just the element itself. The element object receives the Renderer in the accept() method and makes the Renderer do the job of generating HTML. Finally, we get the generated HTML code from the Renderer. Now that we know in a general sense what we need to implement, let’s see how we can do it. 18.2 IMPLEMENTING THE SOLUTION We are using an existing library package to implement something similar on top of it. This will typically involve design patterns like Decorator, Adapter, Facade, and the various factory patterns. These patterns are noninvasive: they can be used to provide the interface and behavior we want without altering the underlying libraries. In this section, we’ll start by designing a general way to wrap the HTML_QuickForm input elements. Then we’ll study how the various input con- trols can be implemented in this way. These elements have to be created, so we’ll look into that next. Validation also needs to be taken care of, and we need to know how to use the form object when defining a form in a template. Since there is not enough space to make everything complete, we end the chapter by discussing some ideas on further development. Listing 18.1 Using a QuickForm Renderer for a single element 420 CHAPTER 18 FORM HANDLING 18.2.1 Wrapping the HTML_QuickForm elements We want the form element objects to have the features we need from HTML_QuickForm and yet be easy to use in a template. That means we have to both use some methods from the QuickForm element and add some methods of our own. That’s a job for one of those undignified Decorators that cannot be redecorated because they change the interface too much. Figure 18.3 shows the structure we’re looking for. We have to be able to set and get the value of the element, but the imple- mentation will vary depending on the type of element. We also want to be able to get the name of the element and to generate complete HTML in case we need it. And we will use the QuickForm default renderer even though it might seem like overkill for just a single element. Listing 18.2 shows the code for the abstract input control. The asHtml() method is basically the same code as in listing 18.1. abstract class AbstractInputControl { protected $quickFormElement; abstract public function setValue($value); abstract public function getValue(); public function asHtml() { $renderer = new HTML_QuickForm_Renderer_Default; $renderer->setElementTemplate("{element}"); $this->quickFormElement->accept($renderer); return $renderer->toHtml(); } public function getName() { return $this->quickFormElement->getName(); } } Figure 18.3 How our input control wraps the Quickfom element Listing 18.2 The input control parent class acts as a Decorator for the HTML_QuickForm element and also uses a default Renderer to gen- erate HTML for the element IMPLEMENTING THE SOLUTION 421 We want to be able to get both the value and the name of the element. The get- Name() method is the same for all element types, but getValue() needs to be in the child classes, since there are minor differences in how they work. 18.2.2 Input controls The input controls are different in principle; this fact is what our abstractions express. This means that they have to be handled differently. Text input elements (text areas and text input boxes) are relatively simple, but need validation. Select menus come in two flavors: single and multiple selection. Radio buttons are especially difficult, since HTML_QuickForm has no explicit support for such features as setting the value of a radio button group. Text input elements Text input fields need validation. So do select fields, but not in quite the same way. (Menus and the like cannot be filled in incorrectly by the user, but this can be bypassed by sending an HTTP request by some other means than the form that’s intended for it. But to simplify our job for now, we’ll assume that we’re doing valida- tion for usability only, which means that text input elements are the only ones on our radar screen.) We must be able to specify the validation type and the message to be displayed if validation fails. We’ll look at how to make HTML_QuickForm do the actual validation later; for now, we’ll do the basic part, using the QuickForm object as a data container whose values can be accessed in a template. Listing 18.3 is a TextInput class that does this somewhat basic job. class TextInput extends AbstractInputControl { protected $quickFormElement; private $validation; private $message; public function __construct( $quickFormElement,$validation,$message) { $this->validation = $validation; $this->message = $message; $this->quickFormElement = $quickFormElement; } public function setValue($value) { $this->quickFormElement->setValue($value); } public function getValue() { return $this->quickFormElement->getValue(); } Listing 18.3 A TextInput class that contains an HTML_QuickForm_text object b The element we’re decorating c Validation type and message d Pass everything in the constructor e Simple delegation 422 CHAPTER 18 FORM HANDLING public function getLabel() { return $this->quickFormElement->getLabel(); } } b $quickFormElement is the element we’re decorating. It contains the name, label, value, and potentially other information, but no validation information thus far. C $validation is the validation type; $message is the message to display if valida- tion fails. D All the member variables are set by passing them as arguments to the constructor. One of these arguments is the QuickForm element object. As we’ve seen in several cases before, this is a way to create flexibility. We can configure the QuickForm ele- ment object before inserting it into our own object. E setValue(), getValue(), and getLabel() just call the decorated Quick- Form element object in the simplest possible way. Select menus Since it has no validation to do, the select menu class is even simpler than the text input class. But just to make the design clearer, let’s have an abstract class to represent the similarity between a select menu and a radio button group. We’ll call it Select- One. That gets us the class design shown in figure 18.4. The abstract class is simple: abstract class SelectOne extends AbstractInputControl { protected $quickFormElement; e Simple delegation Figure 18.4 The inheritance hierarchy for the elements illustrates abstract similarities and differences IMPLEMENTING THE SOLUTION 423 public function __construct($quickFormElement) { $this->quickFormElement = $quickFormElement; } abstract public function addOption($text,$value); } The SelectMenu class (see listing 18.4) is not much larger, mainly because the HTML_QuickForm_select object does so much of the job for us. class SelectMenu extends SelectOne{ protected $quickFormElement; public function addOption($text,$value) { $this->quickFormElement->addOption($text,$value); } public function setValue($value) { $this->quickFormElement->setValue($value); } public function getValue() { $values = $this->quickFormElement->getValue(); return $values[0]; } } b A select menu is more complex than a text input field in one respect: we need to add options. But since the QuickForm has this feature already, it’s not hard to implement. C getValue() in the QuickForm element is based on a different concept than ours. QuickForm sees all <select> elements—single-input and multi-input—as two dif- ferent variations on the same object. We are taking a different and more user-oriented perspective: A single-select and a multi-select are different, while the single-select is basically the same as a group of radio buttons. Because of this mismatch, we won’t use the getValue() of the QuickForm ele- ment directly. It returns an array of selected values. Since our class represents an element that can have only one value, it’s natural to return only the first element in this array. Radio buttons Representing a group of radio buttons is somewhat more complex, because there is no direct support for it in HTML_QuickForm. There is a class representing a group of elements and one representing a radio button. But there is no implementation of the actual workings of a group of radio buttons. Therefore, we need some more code in this case (see listing 18.5). Listing 18.4 A SelectMenu class that contains an HTML_QuickForm_select object b Adding options is easy c Single select returns single value 424 CHAPTER 18 FORM HANDLING class RadioButtonGroup extends SelectOne { private $value; private $options = array(); protected $quickFormElement; public function addOption($text,$value) { $elements = $this->quickFormElement->getElements(); $element = new HTML_QuickForm_radio( NULL,$text,NULL,$value); $elements[] = $element; $this->quickFormElement->setElements($elements); $this->options[$value] = $element; } public function setValue($value) { $this->value = $value; if (!array_key_exists($value,$this->options)) return; $this->options[$value]->setChecked(TRUE); } public function getValue() { return $this->value; } public function options() { return $this->options; } } b Since HTML_QuickForm does not have a representation of how radio buttons work, we need to handle more of it ourselves. To make this possible, we keep both the value and the button objects themselves in instance variables. C For some reason, there is no way to add an element to a QuickForm group. Instead, we have to get all the elements as an array, add a new one to the array, and put the whole array back. D To set the value of a radio button group, we set the checked property of the radio button representing that value. We also store the value separately to make it easy to retrieve. setValue() for the QuickForm group does something different, which is not what we need here. Listing 18.5 A RadioButtonGroup class b Keep value and options separately c Add by replacing all d Set value in both places [...]... more complex, using progressively more advanced design patterns including Singleton, Registry, and Service Locator In the next chapter, we continue our journey into the world of object-oriented data access, looking closely at the challenges of encapsulating, hiding, generating, and generalizing SQL 448 CHAPTER 19 DATABASE CONNECTION, ABSTRACTION, AND CONFIGURATION P A R T 4 Databases and infrastructure... H A P T E R 1 9 Database connection, abstraction, and configuration 19. 1 19. 2 19. 3 19. 4 Database abstraction 433 Decorating and adapting database resource objects Making the database connection available 442 Summary 448 438 Databases are a major component of any programmer’s diet PHP programmers eat databases for breakfast, not to mention lunch, dinner, and late-night snacks With MySQL in particular... to those we saw in the section on synchronizing server-side and client-side validation Our current way of inserting values into to the template file adds little actual information to the HTML markup Take this excerpt from listing 18.6: Headline (this is sample text that PHPTAL removes) connection; } } The design of this class is almost identical to the example in Martin Fowler’s article on Dependency Injection [Fowler DI] This variation on the Singleton pattern is interesting Unlike the standard Singleton pattern, the instance is hidden inside the class and there is no way to return it And we can set the single instance: ServiceLocator::load(... value="${elements/headline/getValue}" /> Repeating the elements/headline path is just a way of making this intelligible to the template engine It’s a text input field called headline; that’s the gist of it If we simplify the task by ignoring the label at first, we could generate the above from this basic markup: All we would need to do is to pre-process the template using our Form... design is illustrated in figure 19. 2 The complete interface of the MySqlResultIterator class is given by the SPL Iterator interface, which is built into PHP Listing 19. 4 shows the result iterator class Figure 19. 2 Design for an SPL iterator for MySQL Listing 19. 4 Database result iterator class that implements the SPL Iterator interface so that it can be used in a foreach loop class MysqlResultIterator implements... message may be hard to read This is not terribly problematic, but it is inconvenient, and can we avoid it by using prepared statements 436 CHAPTER 19 DATABASE CONNECTION, ABSTRACTION, AND CONFIGURATION 19. 1.2 Object-oriented database querying Database querying in PHP abounds with simple and complex abstraction layers There is not one single, unified package that supports all different databases and database... glue of our own to make the existing packages do our bidding smoothly To that purpose, it helps to have a good conceptual understanding of how the existing packages are designed Listing 19. 3 shows yet another version of the prepared statement package, this time using Creole, which is inspired by (and very similar to) Java’s JDBC Listing 19. 3 Prepared statement example using Creole try { $conn = Creole::getConnection(... often starting with fetch or get That’s why only the responsibilities are noted in the figure What PDO does (see listing 19. 2) is merge the prepared statement and the result set into a single object, although the query and the results from that query are Figure 19. 1 Typical conceptual structure for object-oriented database querying DATABASE ABSTRACTION 437 conceptually very different things That’s... the inheritance relationship involves a degree of coupling that is not ideal when we don’t control the classes we are inheriting from The alternative, then, is to use Decorators We jumped the gun in chapter 7 by looking at a couple of examples of what Nock calls the Resource Decorator pattern, decorating a PEAR DB object to log calls and generate exceptions One thing to be aware of when decorating . the following: • A text input object. We don’t need to distinguish between single-line text input controls and text areas at all in our PHP code, although we will, of course, do so in the HTML. of using a package that has components baked into it in a way that makes them hard to separate. We do get something for free, but beyond a certain point, there are diminishing returns: trying. configuration. 432 CHAPTER 19 Database connection, abstraction, and configuration 19. 1 Database abstraction 433 19. 2 Decorating and adapting database resource objects 438 19. 3 Making the database connection

Ngày đăng: 12/08/2014, 21:21