<?php with no space between the <? and php.I'm fine contains a ', it should be surrounded by double quotes ("I'm fine") or the ' should be escaped ('I\'m fine').?>, not ??>. Or, if this code were the last thing in its file, the closing PHP tag could be omitted.$hamburger=4.95;$shake=1.95;$cola=0.85;$tip_rate=0.16;$tax_rate=0.075;$food=(2*$hamburger)+$shake+$cola;$tip=$food*$tip_rate;$tax=$food*$tax_rate;$total=$food+$tip+$tax;'The total cost of the meal is $'.$total;
$hamburger=4.95;$shake=1.95;$cola=0.85;$tip_rate=0.16;$tax_rate=0.075;$food=(2*$hamburger)+$shake+$cola;$tip=$food*$tip_rate;$tax=$food*$tax_rate;$total=$food+$tip+$tax;printf("%d %-9s at\$%.2f each:\$%5.2f\n",2,'Hamburger',$hamburger,2*$hamburger);printf("%d %-9s at\$%.2f each:\$%5.2f\n",1,'Shake',$shake,$hamburger);printf("%d %-9s at\$%.2f each:\$%5.2f\n",1,'Cola',$cola,$cola);printf("%25s:\$%5.2f\n",'Food Total',$food);printf("%25s:\$%5.2f\n",'Food and Tax Total',$food+$tax);printf("%25s:\$%5.2f\n",'Food, Tax, and Tip Total',$total);
$first_name='Srinivasa';$last_name='Ramanujan';$name="$first_name$last_name";$name;strlen($name);
$n=1;$p=2;"$n,$p\n";$n++;$p*=2;"$n,$p\n";$n++;$p*=2;"$n,$p\n";$n++;$p*=2;"$n,$p\n";$n++;$p*=2;"$n,$p\n";
falsetruetruefalsefalsetruetruefalseMessage 3.Age: 12. Shoe Size: 14
$f=-50;while($f<=50){$c=($f-32)*(5/9);printf("%d degrees F = %d degrees C\n",$f,$c);$f+=5;}
for($f=-50;$f<=50;$f+=5){$c=($f-32)*(5/9);printf("%d degrees F = %d degrees C\n",$f,$c);}
<table><tr><th>City</th><th>Population</th></tr><?php$census=['New York, NY'=>8175133,'Los Angeles, CA'=>3792621,'Chicago, IL'=>2695598,'Houston, TX'=>2100263,'Philadelphia, PA'=>1526006,'Phoenix, AZ'=>1445632,'San Antonio, TX'=>1327407,'San Diego, CA'=>1307402,'Dallas, TX'=>1197816,'San Jose, CA'=>945942];$total=0;foreach($censusas$city=>$population){$total+=$population;"<tr><td>$city</td><td>$population</td></tr>\n";}"<tr><td>Total</td><td>$total</td></tr>\n";"</table>";
$census=['New York, NY'=>8175133,'Los Angeles, CA'=>3792621,'Chicago, IL'=>2695598,'Houston, TX'=>2100263,'Philadelphia, PA'=>1526006,'Phoenix, AZ'=>1445632,'San Antonio, TX'=>1327407,'San Diego, CA'=>1307402,'Dallas, TX'=>1197816,'San Jose, CA'=>945942];// Sort the associative array by valueasort($census);"<table>\n";"<tr><th>City</th><th>Population</th></tr>\n";$total=0;foreach($censusas$city=>$population){$total+=$population;"<tr><td>$city</td><td>$population</td></tr>\n";}"<tr><td>Total</td><td>$total</td></tr>\n";"</table>";// Sort the associative array by keyksort($census);"<table>\n";"<tr><th>City</th><th>Population</th></tr>\n";$total=0;foreach($censusas$city=>$population){$total+=$population;"<tr><td>$city</td><td>$population</td></tr>\n";}"<tr><td>Total</td><td>$total</td></tr>\n";"</table>";
<table><tr><th>City</th><th>Population</th></tr><?php// Each element in $census is a three-element array// containing city name, state, and population$census=[['New York','NY',8175133],['Los Angeles','CA',3792621],['Chicago','IL',2695598],['Houston','TX',2100263],['Philadelphia','PA',1526006],['Phoenix','AZ',1445632],['San Antonio','TX',1327407],['San Diego','CA',1307402],['Dallas','TX',1197816],['San Jose','CA',945942]];$total=0;$state_totals=array();foreach($censusas$city_info){// Update the total population$total+=$city_info[2];// If we haven't seen this state yet, initialize its// population total to 0if(!array_key_exists($city_info[1],$state_totals)){$state_totals[$city_info[1]]=0;}// Update the per-state population$state_totals[$city_info[1]]+=$city_info[2];"<tr><td>$city_info[0],$city_info[1]</td><td>$city_info[2]</td></tr>\n";}"<tr><td>Total</td><td>$total</td></tr>\n";// Print the per-state totalsforeach($state_totalsas$state=>$population){"<tr><td>$state</td><td>$population</td></tr>\n";}"</table>";
/* The grades and ID numbers of students in a class:An associative array whose key is the student's name and whose value isan associative array of grade and ID number*/$students=['James D. McCawley'=>['grade'=>'A+','id'=>271231],'Buwei Yang Chao'=>['grade'=>'A','id'=>818211]];/* How many of each item in a store inventory are in stock:An associative array whose key is the item name and whose value is thenumber in stock*/$inventory=['Wok'=>5,'Steamer'=>3,'Heavy Cleaver'=>3,'Light Cleaver'=>0];/* School lunches for a week — the different parts of each meal(entree, side dish, drink, etc.) and the cost for each day:An associative array whose key is the day and whose value is anassociative array describing the meal. This associative array has a key/valuepair for cost and a key/value pair for each part of the meal.*/$lunches=['Monday'=>['cost'=>1.50,'entree'=>'Beef Shu-Mai','side'=>'Salty Fried Cake','drink'=>'Black Tea'],'Tuesday'=>['cost'=>2.50,'entree'=>'Clear-steamed Fish','side'=>'Turnip Cake','drink'=>'Bubble Tea'],'Wednesday'=>['cost'=>2.00,'entree'=>'Braised Sea Cucumber','side'=>'Turnip Cake','drink'=>'Green Tea'],'Thursday'=>['cost'=>1.35,'entree'=>'Stir-fried Two Winters','side'=>'Egg Puff','drink'=>'Black Tea'],'Friday'=>['cost'=>3.25,'entree'=>'Stewed Pork with Taro','side'=>'Duck Feet','drink'=>'Jasmine Tea']];/* The names of people in your family:A numeric array whose indices are implicit and whose values are the namesof family members*/$family=['Bart','Lisa','Homer','Marge','Maggie'];/* The names, ages, and relationship to you of people in your family:An associative array whose keys are the names of family members and whosevalues are associative arrays with age and relationship key/value pairs*/$family=['Bart'=>['age'=>10,'relation'=>'brother'],'Lisa'=>['age'=>7,'relation'=>'sister'],'Homer'=>['age'=>36,'relation'=>'father'],'Marge'=>['age'=>34,'relation'=>'mother'],'Maggie'=>['age'=>1,'relation'=>'self']];
functionhtml_img($url,$alt=null,$height=null,$width=null){$html='<img src="'.$url.'"';if(isset($alt)){$html.=' alt="'.$alt.'"';}if(isset($height)){$html.=' height="'.$height.'"';}if(isset($width)){$html.=' width="'.$width.'"';}$html.='/>';return$html;}
functionhtml_img2($file,$alt=null,$height=null,$width=null){if(isset($GLOBALS['image_path'])){$file=$GLOBALS['image_path'].$file;}$html='<img src="'.$file.'"';if(isset($alt)){$html.=' alt="'.$alt.'"';}if(isset($height)){$html.=' height="'.$height.'"';}if(isset($width)){$html.=' width="'.$width.'"';}$html.='/>';return$html;}
// The html_img2() function from the previous exercise is saved in this fileinclude"html-img2.php";$image_path='/images/';html_img2('puppy.png');html_img2('kitten.png','fuzzy');html_img2('dragon.png',null,640,480);
I can afford a tip of 11% (30) I can afford a tip of 12% (30.25) I can afford a tip of 13% (30.5) I can afford a tip of 14% (30.75)
/* Using dechex(): */functionweb_color1($red,$green,$blue){$hex=[dechex($red),dechex($green),dechex($blue)];// Prepend a leading 0 if necessary to 1-digit hex valuesforeach($hexas$i=>$val){if(strlen($i)==1){$hex[$i]="0$val";}}return'#'.implode('',$hex);}/* You can also rely on sprintf()'s %x format character to dohex-to-decimal conversion: */functionweb_color2($red,$green,$blue){returnsprintf('#%02x%02x%02x',$red,$green,$blue);}
classIngredient{protected$name;protected$cost;publicfunction__construct($name,$cost){$this->name=$name;$this->cost=$cost;}publicfunctiongetName(){return$this->name;}publicfunctiongetCost(){return$this->cost;}}
classIngredient{protected$name;protected$cost;publicfunction__construct($name,$cost){$this->name=$name;$this->cost=$cost;}publicfunctiongetName(){return$this->name;}publicfunctiongetCost(){return$this->cost;}// This method sets the cost to a new valuepublicfunctionsetCost($cost){$this->cost=$cost;}}
classPricedEntreeextendsEntree{publicfunction__construct($name,$ingredients){parent::__construct($name,$ingredients);foreach($this->ingredientsas$ingredient){if(!$ingredientinstanceofIngredient){thrownewException('Elements of $ingredients must beIngredient objects');}}}publicfunctiongetCost(){$cost=0;foreach($this->ingredientsas$ingredient){$cost+=$ingredient->getCost();}return$cost;}}
The Ingredient class in its own namespace:
namespaceMeals;classIngredient{protected$name;protected$cost;publicfunction__construct($name,$cost){$this->name=$name;$this->cost=$cost;}publicfunctiongetName(){return$this->name;}publicfunctiongetCost(){return$this->cost;}// This method sets the cost to a new valuepublicfunctionsetCost($cost){$this->cost=$cost;}}
The PricedEntree class referencing that namespace:
classPricedEntreeextendsEntree{publicfunction__construct($name,$ingredients){parent::__construct($name,$ingredients);foreach($this->ingredientsas$ingredient){if(!$ingredientinstanceof\Meals\Ingredient){thrownewException('Elements of $ingredients must beIngredient objects');}}}publicfunctiongetCost(){$cost=0;foreach($this->ingredientsas$ingredient){$cost+=$ingredient->getCost();}return$cost;}}
$_POST['noodle']='barbecued pork';$_POST['sweet']=['puff','ricemeat'];$_POST['sweet_q']='4';$_POST['submit']='Order';
/* Since this is operating on form data, it looks directly at $_POSTinstead of a validated $input array */functionprocess_form(){'<ul>';foreach($_POSTas$k=>$v){'<li>'.htmlentities($k).'='.htmlentities($v).'</li>';}'</ul>';}
<?php// This assumes FormHelper.php is in the same directory as// this file.require'FormHelper.php';// Set up the arrays of choices in the select menu.// This is needed in display_form(), validate_form(),// and process_form(), so it is declared in the global scope.$ops=array('+','-','*','/');// The main page logic:// - If the form is submitted, validate and then process or redisplay// - If it's not submitted, displayif($_SERVER['REQUEST_METHOD']=='POST'){// If validate_form() returns errors, pass them to show_form()list($errors,$input)=validate_form();if($errors){show_form($errors);}else{// The submitted data is valid, so process itprocess_form($input);// And then show the form again to do another calculationshow_form();}}else{// The form wasn't submitted, so displayshow_form();}functionshow_form($errors=array()){$defaults=array('num1'=>2,'op'=>2,// the index of '*' in $ops'num2'=>8);// Set up the $form object with proper defaults$form=newFormHelper($defaults);// All the HTML and form display is in a separate file for clarityinclude'math-form.php';}functionvalidate_form(){$input=array();$errors=array();// op is required$input['op']=$GLOBALS['ops'][$_POST['op']]??'';if(!in_array($input['op'],$GLOBALS['ops'])){$errors[]='Please select a valid operation.';}// num1 and num2 must be numbers$input['num1']=filter_input(INPUT_POST,'num1',FILTER_VALIDATE_FLOAT);if(is_null($input['num1'])||($input['num1']===false)){$errors[]='Please enter a valid first number.';}$input['num2']=filter_input(INPUT_POST,'num2',FILTER_VALIDATE_FLOAT);if(is_null($input['num2'])||($input['num2']===false)){$errors[]='Please enter a valid second number.';}// Can't divide by zeroif(($input['op']=='/')&&($input['num2']==0)){$errors[]='Division by zero is not allowed.';}returnarray($errors,$input);}functionprocess_form($input){$result=0;if($input['op']=='+'){$result=$input['num1']+$input['num2'];}elseif($input['op']=='-'){$result=$input['num1']-$input['num2'];}elseif($input['op']=='*'){$result=$input['num1']*$input['num2'];}elseif($input['op']=='/'){$result=$input['num1']/$input['num2'];}$message="{$input['num1']}{$input['op']}{$input['num2']}=$result";"<h3>$message</h3>";}?>
The code relies on the FormHelper.php file discussed in Chapter 7. The math-form.php file referenced, which displays the form HTML, contains:
<formmethod="POST"action="<?=$form->encode($_SERVER['PHP_SELF']) ?>"><table><?phpif($errors){?><tr><td>You need to correct the following errors:</td><td><ul><?phpforeach($errorsas$error){?><li><?=$form->encode($error)?></li><?php}?></ul></td><?php}?><tr><td>First Number:</td><td><?=$form->input('text',['name'=>'num1'])?></td></tr><tr><td>Operation:</td><td><?=$form->select($GLOBALS['ops'],['name'=>'op'])?></td></tr><tr><td>Second Number:</td><td><?=$form->input('text',['name'=>'num2'])?></td></tr><tr><td colspan="2" align="center"><?=$form->input('submit',['value'=>'Calculate'])?></td></tr></table></form>
<?php// This assumes FormHelper.php is in the same directory as// this file.require'FormHelper.php';// Set up the array of choices in the select menu.// This is needed in display_form(), validate_form(),// and process_form(), so it is declared in the global scope.$states=['AL','AK','AZ','AR','CA','CO','CT','DC','DE','FL','GA','HI','ID','IL','IN','IA','KS','KY','LA','ME','MD','MA','MI','MN','MS','MO','MT','NE','NV','NH','NJ','NM','NY','NC','ND','OH','OK','OR','PA','RI','SC','SD','TN','TX','UT','VT','VA','WA','WV','WI','WY'];// The main page logic:// - If the form is submitted, validate and then process or redisplay// - If it's not submitted, displayif($_SERVER['REQUEST_METHOD']=='POST'){// If validate_form() returns errors, pass them to show_form()list($errors,$input)=validate_form();if($errors){show_form($errors);}else{// The submitted data is valid, so process itprocess_form($input);}}else{// The form wasn't submitted, so displayshow_form();}functionshow_form($errors=array()){// Set up the $form object with proper defaults$form=newFormHelper();// All the HTML and form display is in a separate file for clarityinclude'shipping-form.php';}functionvalidate_form(){$input=array();$errors=array();foreach(['from','to']as$addr){// Check required fieldsforeach(['Name'=>'name','Address 1'=>'address1','City'=>'city','State'=>'state']as$label=>$field){$input[$addr.'_'.$field]=$_POST[$addr.'_'.$field]??'';if(strlen($input[$addr.'_'.$field])==0){$errors[]="Please enter a value for$addr$label.";}}// Check state$input[$addr.'_state']=$GLOBALS['states'][$input[$addr.'_state']]??'';if(!in_array($input[$addr.'_state'],$GLOBALS['states'])){$errors[]="Please select a valid$addrstate.";}// Check zip code$input[$addr.'_zip']=filter_input(INPUT_POST,$addr.'_zip',FILTER_VALIDATE_INT,['options'=>['min_range'=>10000,'max_range'=>99999]]);if(is_null($input[$addr.'_zip'])||($input[$addr.'_zip']===false)){$errors[]="Please enter a valid$addrZIP";}// Don't forget about address2!$input[$addr.'_address2']=$_POST[$addr.'_address2']??'';}// height, width, depth, weight must all be numbers > 0foreach(['height','width','depth','weight']as$field){$input[$field]=filter_input(INPUT_POST,$field,FILTER_VALIDATE_FLOAT);// Since 0 is not valid, we can just test for truth rather than// null or exactly falseif(!($input[$field]&&($input[$field]>0))){$errors[]="Please enter a valid$field.";}}// Check weightif($input['weight']>150){$errors[]="The package must weigh no more than 150 lbs.";}// Check dimensionsforeach(['height','width','depth']as$dim){if($input[$dim]>36){$errors[]="The package$dimmust be no more than 36 inches.";}}returnarray($errors,$input);}functionprocess_form($input){// Make a template for the report$tpl=<<<HTML<p>Yourpackageis{height}" x {width}"x{depth}" and weighs {weight} lbs.</p><p>It is coming from:</p><pre>{from_name}{from_address}{from_city}, {from_state} {from_zip}</pre><p>It is going to:</p><pre>{to_name}{to_address}{to_city}, {to_state} {to_zip}</pre>HTML;// Adjust addresses in$inputfor easier outputforeach(['from','to'] as$addr) {$input[$addr.'_address']=$input[$addr.'_address1'];if (strlen($input[$addr.'_address2'])) {$input[$addr.'_address'].= "\n" .$input[$addr.'_address2'];}}// Replace each template variable with the corresponding value// in$input$html=$tpl;foreach($inputas$k=>$v) {$html= str_replace('{'.$k.'}',$v,$html);}// Print the report$html;}?>
The code relies on the FormHelper.php file discussed in Chapter 7. The shipping-form.php file referenced, which displays the form HTML, contains:
<formmethod="POST"action="<?=$form->encode($_SERVER['PHP_SELF']) ?>"><table><?phpif($errors){?><tr><td>You need to correct the following errors:</td><td><ul><?phpforeach($errorsas$error){?><li><?=$form->encode($error)?></li><?php}?></ul></td><?php}?><tr><th>From:</th><td></td></tr><tr><td>Name:</td><td><?=$form->input('text',['name'=>'from_name'])?></td></tr><tr><td>Address 1:</td><td><?=$form->input('text',['name'=>'from_address1'])?></td></tr><tr><td>Address 2:</td><td><?=$form->input('text',['name'=>'from_address2'])?></td></tr><tr><td>City:</td><td><?=$form->input('text',['name'=>'from_city'])?></td></tr><tr><td>State:</td><td><?=$form->select($GLOBALS['states'],['name'=>'from_state'])?></td></tr><tr><td>ZIP:</td><td><?=$form->input('text',['name'=>'from_zip','size'=>5])?></td></tr><tr><th>To:</th><td></td></tr><tr><td>Name:</td><td><?=$form->input('text',['name'=>'to_name'])?></td></tr><tr><td>Address 1:</td><td><?=$form->input('text',['name'=>'to_address1'])?></td></tr><tr><td>Address 2:</td><td><?=$form->input('text',['name'=>'to_address2'])?></td></tr><tr><td>City:</td><td><?=$form->input('text',['name'=>'to_city'])?></td></tr><tr><td>State:</td><td><?=$form->select($GLOBALS['states'],['name'=>'to_state'])?></td></tr><tr><td>ZIP:</td><td><?=$form->input('text',['name'=>'to_zip','size'=>5])?></td></tr><tr><th>Package:</th><td></td></tr><tr><td>Weight:</td><td><?=$form->input('text',['name'=>'weight'])?></td></tr><tr><td>Height:</td><td><?=$form->input('text',['name'=>'height'])?></td></tr><tr><td>Width:</td><td><?=$form->input('text',['name'=>'width'])?></td></tr><tr><td>Depth:</td><td><?=$form->input('text',['name'=>'depth'])?></td></tr><tr><td colspan="2" align="center"><?=$form->input('submit',['value'=>'Ship!'])?></td></tr></table></form>
functionprint_array($ar){'<ul>';foreach($aras$k=>$v){if(is_array($v)){'<li>'.htmlentities($k).':</li>';print_array($v);}else{'<li>'.htmlentities($k).'='.htmlentities($v).'</li>';}}'</ul>';}/* Since this is operating on form data, it looks directly at $_POSTinstead of a validated $input array */functionprocess_form(){print_array($_POST);}
try{// Connect$db=newPDO('sqlite:/tmp/restaurant.db');// Set up exceptions on DB errors$db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);$stmt=$db->query('SELECT * FROM dishes ORDER BY price');$dishes=$stmt->fetchAll();if(count($dishes)==0){$html='<p>No dishes to display</p>';}else{$html="<table>\n";$html.="<tr><th>Dish Name</th><th>Price</th><th>Spicy?</th></tr>\n";foreach($dishesas$dish){$html.='<tr><td>'.htmlentities($dish['dish_name']).'</td><td>$'.sprintf('%.02f',$dish['price']).'</td><td>'.($dish['is_spicy']?'Yes':'No')."</td></tr>\n";}$html.="</table>";}}catch(PDOException$e){$html="Can't show dishes: ".$e->getMessage();}$html;
<?php// Load the form helper classrequire'FormHelper.php';// Connect to the databasetry{$db=newPDO('sqlite:/tmp/restaurant.db');}catch(PDOException$e){"Can't connect: ".$e->getMessage();exit();}// Set up exceptions on DB errors$db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);// Set up fetch mode: rows as objects$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_OBJ);// The main page logic:// - If the form is submitted, validate and then process or redisplay// - If it's not submitted, displayif($_SERVER['REQUEST_METHOD']=='POST'){// If validate_form() returns errors, pass them to show_form()list($errors,$input)=validate_form();if($errors){show_form($errors);}else{// The submitted data is valid, so process itprocess_form($input);}}else{// The form wasn't submitted, so displayshow_form();}functionshow_form($errors=array()){// Set up the $form object with proper defaults$form=newFormHelper();// All the HTML and form display is in a separate file for clarityinclude'price-form.php';}functionvalidate_form(){$input=array();$errors=array();// Minimum price must be a valid floating-point number$input['min_price']=filter_input(INPUT_POST,'min_price',FILTER_VALIDATE_FLOAT);if($input['min_price']===null||$input['min_price']===false){$errors[]='Please enter a valid minimum price.';}returnarray($errors,$input);}functionprocess_form($input){// Access the global variable $db inside this functionglobal$db;// Build up the query$sql='SELECT dish_name, price, is_spicy FROM dishes WHEREprice >= ?';// Send the query to the database program and get all the rows back$stmt=$db->prepare($sql);$stmt->execute(array($input['min_price']));$dishes=$stmt->fetchAll();if(count($dishes)==0){'No dishes matched.';}else{'<table>';'<tr><th>Dish Name</th><th>Price</th><th>Spicy?</th></tr>';foreach($dishesas$dish){if($dish->is_spicy==1){$spicy='Yes';}else{$spicy='No';}printf('<tr><td>%s</td><td>$%.02f</td><td>%s</td></tr>',htmlentities($dish->dish_name),$dish->price,$spicy);}'</table>';}}?>
The code relies on the FormHelper.php file discussed in Chapter 7. The price-form.php file referenced, which displays the form HTML, contains:
<formmethod="POST"action="<?=$form->encode($_SERVER['PHP_SELF']) ?>"><table><?phpif($errors){?><tr><td>You need to correct the following errors:</td><td><ul><?phpforeach($errorsas$error){?><li><?=$form->encode($error)?></li><?php}?></ul></td><?php}?><tr><td>Minimum Price:</td><td><?=$form->input('text',['name'=>'min_price'])?></td></tr><tr><td colspan="2" align="center"><?=$form->input('submit',['name'=>'search','value'=>'Search'])?></td></tr></table></form>
<?php// Load the form helper classrequire'FormHelper.php';// Connect to the databasetry{$db=newPDO('sqlite:/tmp/restaurant.db');}catch(PDOException$e){"Can't connect: ".$e->getMessage();exit();}// Set up exceptions on DB errors$db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);// Set up fetch mode: rows as objects$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_OBJ);// The main page logic:// - If the form is submitted, validate and then process or redisplay// - If it's not submitted, displayif($_SERVER['REQUEST_METHOD']=='POST'){// If validate_form() returns errors, pass them to show_form()list($errors,$input)=validate_form();if($errors){show_form($errors);}else{// The submitted data is valid, so process itprocess_form($input);}}else{// The form wasn't submitted, so displayshow_form();}functionshow_form($errors=array()){global$db;// Set up the $form object with proper defaults$form=newFormHelper();// Retrieve the list of dish names to use from the database$sql='SELECT dish_id, dish_name FROM dishes ORDER BY dish_name';$stmt=$db->query($sql);$dishes=array();while($row=$stmt->fetch()){$dishes[$row->dish_id]=$row->dish_name;}// All the HTML and form display is in a separate file for clarityinclude'dish-form.php';}functionvalidate_form(){$input=array();$errors=array();// As long as some dish_id value is submitted, we'll consider it OK.// If it doesn't match any dishes in the database, process_form()// can report that.if(isset($_POST['dish_id'])){$input['dish_id']=$_POST['dish_id'];}else{$errors[]='Please select a dish.';}returnarray($errors,$input);}functionprocess_form($input){// Access the global variable $db inside this functionglobal$db;// Build up the query$sql='SELECT dish_id, dish_name, price, is_spicy FROM dishes WHEREdish_id = ?';// Send the query to the database program and get all the rows back$stmt=$db->prepare($sql);$stmt->execute(array($input['dish_id']));$dish=$stmt->fetch();if(count($dish)==0){'No dishes matched.';}else{'<table>';'<tr><th>ID</th><th>Dish Name</th><th>Price</th>';'<th>Spicy?</th></tr>';if($dish->is_spicy==1){$spicy='Yes';}else{$spicy='No';}printf('<tr><td>%d</td><td>%s</td><td>$%.02f</td><td>%s</td></tr>',$dish->dish_id,htmlentities($dish->dish_name),$dish->price,$spicy);'</table>';}}?>
The code relies on the FormHelper.php file discussed in Chapter 7. The dish-form.php file referenced, which displays the form HTML, contains:
<formmethod="POST"action="<?=$form->encode($_SERVER['PHP_SELF']) ?>"><table><?phpif($errors){?><tr><td>You need to correct the following errors:</td><td><ul><?phpforeach($errorsas$error){?><li><?=$form->encode($error)?></li><?php}?></ul></td><?php}?><tr><td>Dish:</td><td><?=$form->select($dishes,['name'=>'dish_id'])?></td></tr><tr><td colspan="2" align="center"><?=$form->input('submit',['name'=>'info','value'=>'Get Dish Info'])?></td></tr></table></form>
<?php// Load the form helper classrequire'FormHelper.php';// Connect to the databasetry{$db=newPDO('sqlite:/tmp/restaurant.db');}catch(PDOException$e){"Can't connect: ".$e->getMessage();exit();}// Set up exceptions on DB errors$db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);// Set up fetch mode: rows as objects$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_OBJ);// Put the list of dish IDs and names in a global array because// we'll need it in show_form() and validate_form()$dishes=array();$sql='SELECT dish_id, dish_name FROM dishes ORDER BY dish_name';$stmt=$db->query($sql);while($row=$stmt->fetch()){$dishes[$row->dish_id]=$row->dish_name;}// The main page logic:// - If the form is submitted, validate and then process or redisplay// - If it's not submitted, displayif($_SERVER['REQUEST_METHOD']=='POST'){// If validate_form() returns errors, pass them to show_form()list($errors,$input)=validate_form();if($errors){show_form($errors);}else{// The submitted data is valid, so process itprocess_form($input);}}else{// The form wasn't submitted, so displayshow_form();}functionshow_form($errors=array()){global$db,$dishes;// Set up the $form object with proper defaults$form=newFormHelper();// All the HTML and form display is in a separate file for clarityinclude'customer-form.php';}functionvalidate_form(){global$dishes;$input=array();$errors=array();// Make sure a dish_id valid is submitted and in $dishes.// As long as some dish_id value is submitted, we'll consider it OK.// If it doesn't match any dishes in the database, process_form()// can report that.$input['dish_id']=$_POST['dish_id']??'';if(!array_key_exists($input['dish_id'],$dishes)){$errors[]='Please select a valid dish.';}// Name is required$input['name']=trim($_POST['name']??'');if(0==strlen($input['name'])){$errors[]='Please enter a name.';}// Phone number is required$input['phone']=trim($_POST['phone']??'');if(0==strlen($input['phone'])){$errors[]='Please enter a phone number.';}else{// Be US-centric and ensure that the phone number contains// at least 10 digits. Using ctype_digit() on each// character is not the most efficient way to do this,// but is logically straightforward and avoids// regular expressions.$digits=0;for($i=0;$i<strlen($input['phone']);$i++){if(ctype_digit($input['phone'][$i])){$digits++;}}if($digits<10){$errors[]='Phone number needs at least ten digits.';}}returnarray($errors,$input);}functionprocess_form($input){// Access the global variable $db inside this functionglobal$db;// Build up the query. No need to specify customer_id because// the database will automatically assign a unique one.$sql='INSERT INTO customers (name,phone,favorite_dish_id) '.'VALUES (?,?,?)';// Send the query to the database program and get all the rows backtry{$stmt=$db->prepare($sql);$stmt->execute(array($input['name'],$input['phone'],$input['dish_id']));'<p>Inserted new customer.</p>';}catch(Exception$e){"<p>Couldn't insert customer:{$e->getMessage()}.</p>";}}?>
The code relies on the FormHelper.php file discussed in Chapter 7. The customer-form.php file referenced, which displays the form HTML, contains:
<formmethod="POST"action="<?=$form->encode($_SERVER['PHP_SELF']) ?>"><table><?phpif($errors){?><tr><td>You need to correct the following errors:</td><td><ul><?phpforeach($errorsas$error){?><li><?=$form->encode($error)?></li><?php}?></ul></td><?php}?><tr><tr><td>Name:</td><td><?=$form->input('text',['name'=>'name'])?></td></tr><tr><td>Phone Number:</td><td><?=$form->input('text',['name'=>'phone'])?></td></tr><tr><td>Favorite Dish:</td><td><?=$form->select($dishes,['name'=>'dish_id'])?></td></tr><tr><td colspan="2" align="center"><?=$form->input('submit',['name'=>'add','value'=>'Add Customer'])?></td></tr></table></form>
The template file, template.html:
<html><head><title>{title}</title></head><body><h1>{headline}</h1><h2>By {byline}</h2><divclass="article">{article}</div><p><small>Page generated: {date}</small></p></body></html>
The PHP program to replace template variables:
$now=newDateTime();// Express the vars as simply as possible, just key => value$vars=array('title'=>'Man Bites Dog','headline'=>'Man and Dog Trapped in Biting Fiasco','byline'=>'Ireneo Funes','article'=><<<_HTML_<p>While walking in the park today, Bioy Casares took a big juicybite out of his dog, Santa's Little Helper. When asked why he didit, Mr. Casares said, "I was hungry."</p>_HTML_,'date'=>$now->format('l, F j, Y'));// Make a version of $vars to match the templating syntax, with// {} around the keys$template_vars=array();foreach($varsas$k=>$v){$template_vars['{'.$k.'}']=$v;}// Load the template$template=file_get_contents('template.html');if($template===false){die("Can't read template.html:$php_errormsg");}// If given an array of strings to look for and an array of replacements,// str_replace() does all the replacements at once for you$html=str_replace(array_keys($template_vars),array_values($template_vars),$template);// Write out the new HTML page$result=file_put_contents('article.html',$html);if($result===false){die("Can't write article.html:$php_errormsg");}
// The array to accumulate address counts$addresses=array();$fh=fopen('addresses.txt','rb');if(!$fh){die("Can't open addresses.txt:$php_errormsg");}while((!feof($fh))&&($line=fgets($fh))){$line=trim($line);// Use the address as the key in $addresses. The value is the number// of times the address has appeared.if(!isset($addresses[$line])){$addresses[$line]=0;}$addresses[$line]=$addresses[$line]+1;}if(!fclose($fh)){die("Can't close addresses.txt:$php_errormsg");}// Reverse sort (biggest first) $addresses by element valuearsort($addresses);$fh=fopen('addresses-count.txt','wb');if(!$fh){die("Can't open addresses-count.txt:$php_errormsg");}foreach($addressesas$address=>$count){// Don't forget the newline at the endif(fwrite($fh,"$count,$address\n")===false){die("Can't write$count,$address:$php_errormsg");}}if(!fclose($fh)){die("Can't close addresses-count.txt:$php_errormsg");}
Here is a sample addresses.txt to use:
brilling@tweedledee.example.com slithy@unicorn.example.com uffish@knight.example.net slithy@unicorn.example.com jubjub@sheep.example.com tumtum@queen.example.org slithy@unicorn.example.com uffish@knight.example.net manxome@king.example.net beamish@lion.example.org uffish@knight.example.net frumious@tweedledum.example.com tulgey@carpenter.example.com vorpal@crow.example.org beamish@lion.example.org mimsy@walrus.example.com frumious@tweedledum.example.com raths@owl.example.net frumious@tweedledum.example.com
$fh=fopen('dishes.csv','rb');if(!$fh){die("Can't open dishes.csv:$php_errormsg");}"<table>\n";while((!feof($fh))&&($line=fgetcsv($fh))){// Using implode() as in Chapter 4"<tr><td>".implode("</td><td>",$line)."</td></tr>\n";}"</table>";
<?php// Load the form helper classrequire'FormHelper.php';// The main page logic:// - If the form is submitted, validate and then process or redisplay// - If it's not submitted, displayif($_SERVER['REQUEST_METHOD']=='POST'){// If validate_form() returns errors, pass them to show_form()list($errors,$input)=validate_form();if($errors){show_form($errors);}else{// The submitted data is valid, so process itprocess_form($input);}}else{// The form wasn't submitted, so displayshow_form();}functionshow_form($errors=array()){// Set up the $form object with proper defaults$form=newFormHelper();// All the HTML and form display is in a separate file for clarityinclude'filename-form.php';}functionvalidate_form(){$input=array();$errors=array();// Make sure a filename is specified$input['file']=trim($_POST['file']??'');if(0==strlen($input['file'])){$errors[]='Please enter a filename.';}else{// Make sure the full filename is under the web// server's document root$full=$_SERVER['DOCUMENT_ROOT'].'/'.$input['file'];// Use realpath() to resolve any .. sequences or// symbolic links$full=realpath($full);if($full===false){$errors[]="Please enter a valid filename.";}else{// Make sure $full begins with the document root directory$docroot_len=strlen($_SERVER['DOCUMENT_ROOT']);if(substr($full,0,$docroot_len)!=$_SERVER['DOCUMENT_ROOT']){$errors[]='File must be under document root.';}else{// If it's OK, put the full path in $input so we can use// it in process_form()$input['full']=$full;}}}returnarray($errors,$input);}functionprocess_form($input){if(is_readable($input['full'])){htmlentities(file_get_contents($input['full']));}else{"Can't read{$input['file']}.";}}?>
The code relies on the FormHelper.php file discussed in Chapter 7. The filename-form.php file referenced, which displays the form HTML, contains:
<formmethod="POST"action="<?=$form->encode($_SERVER['PHP_SELF']) ?>"><table><?phpif($errors){?><tr><td>You need to correct the following errors:</td><td><ul><?phpforeach($errorsas$error){?><li><?=$form->encode($error)?></li><?php}?></ul></td><?php}?><tr><td>File:</td><td><?=$form->input('text',['name'=>'file'])?></td></tr><tr><td colspan="2"align="center"><?=$form->input('submit',['value'=>'Display'])?></td></tr></table></form>
Here is the new validate_form() function that implements the additional test using strcasecmp():
functionvalidate_form(){$input=array();$errors=array();// Make sure a filename is specified$input['file']=trim($_POST['file']??'');if(0==strlen($input['file'])){$errors[]='Please enter a filename.';}else{// Make sure the full filename is under the web// server's document root$full=$_SERVER['DOCUMENT_ROOT'].'/'.$input['file'];// Use realpath() to resolve any .. sequences or// symbolic links$full=realpath($full);if($full===false){$errors[]="Please enter a valid filename.";}else{// Make sure $full begins with the document root directory$docroot_len=strlen($_SERVER['DOCUMENT_ROOT']);if(substr($full,0,$docroot_len)!=$_SERVER['DOCUMENT_ROOT']){$errors[]='File must be under document root.';}elseif(strcasecmp(substr($full,-5),'.html')!=0){$errors[]='File name must end in .html';}else{// If it's OK, put the full path in $input so we can use// it in process_form()$input['full']=$full;}}}returnarray($errors,$input);}
$view_count=1+($_COOKIE['view_count']??0);setcookie('view_count',$view_count);"<p>Hi! Number of times you've viewed this page:$view_count.</p>";
$view_count=1+($_COOKIE['view_count']??0);if($view_count==20){// An empty value for setcookie() removes the cookiesetcookie('view_count','');$msg="<p>Time to start over.</p>";}else{setcookie('view_count',$view_count);$msg="<p>Hi! Number of times you've viewed this page:$view_count.</p>";if($view_count==5){$msg.="<p>This is your fifth visit.</p>";}elseif($view_count==10){$msg.="<p>This is your tenth visit. You must like this page.</p>";}elseif($view_count==15){$msg.="<p>This is your fifteenth visit. "."Don't you have anything else to do?</p>";}}$msg;
The color-picking page:
<?php// Start sessions first thing so we can use $_SESSION freely latersession_start();// Load the form helper classrequire'FormHelper.php';$colors=array('ff0000'=>'Red','ffa500'=>'Orange','ffffff'=>'Yellow','008000'=>'Green','0000ff'=>'Blue','4b0082'=>'Indigo','663399'=>'Rebecca Purple');// The main page logic:// - If the form is submitted, validate and then process or redisplay// - If it's not submitted, displayif($_SERVER['REQUEST_METHOD']=='POST'){// If validate_form() returns errors, pass them to show_form()list($errors,$input)=validate_form();if($errors){show_form($errors);}else{// The submitted data is valid, so process itprocess_form($input);}}else{// The form wasn't submitted, so displayshow_form();}functionshow_form($errors=array()){global$colors;// Set up the $form object with proper defaults$form=newFormHelper();// All the HTML and form display is in a separate file for clarityinclude'color-form.php';}functionvalidate_form(){$input=array();$errors=array();// color must be a valid color$input['color']=$_POST['color']??'';if(!array_key_exists($input['color'],$GLOBALS['colors'])){$errors[]='Please select a valid color.';}returnarray($errors,$input);}functionprocess_form($input){global$colors;$_SESSION['background_color']=$input['color'];'<p>Your color has been set.</p>';}?>
The code relies on the FormHelper.php file discussed in Chapter 7. The color-form.php file referenced, which displays the form HTML, contains:
<formmethod="POST"action="<?=$form->encode($_SERVER['PHP_SELF']) ?>"><table><?phpif($errors){?><tr><td>You need to correct the following errors:</td><td><ul><?phpforeach($errorsas$error){?><li><?=$form->encode($error)?></li><?php}?></ul></td><?php}?><tr><td>Favorite Color:</td><td><?=$form->select($colors,['name'=>'color'])?></td></tr><tr><td colspan="2" align="center"><?=$form->input('submit',['name'=>'set','value'=>'Set Color'])?></td></tr></table></form>
The page with background color set:
<?php// Start sessions first thing so we can use $_SESSION freely latersession_start();?><html><head><title>Background Color Example</title><body style="background-color:<?=$_SESSION['background_color']?>"><p>What color did you pick?</p></body></html>
The ordering page:
session_start();// This assumes FormHelper.php is in the same directory as// this file.require'FormHelper.php';// Set up the array of choices in the select menu.// This is needed in display_form(), validate_form(),// and process_form(), so it is declared in the global scope.$products=['cuke'=>'Braised Sea Cucumber','stomach'=>"Sauteed Pig's Stomach",'tripe'=>'Sauteed Tripe with Wine Sauce','taro'=>'Stewed Pork with Taro','giblets'=>'Baked Giblets with Salt','abalone'=>'Abalone with Marrow and Duck Feet'];// The main page logic:// - If the form is submitted, validate and then process or redisplay// - If it's not submitted, displayif($_SERVER['REQUEST_METHOD']=='POST'){// If validate_form() returns errors, pass them to show_form()list($errors,$input)=validate_form();if($errors){show_form($errors);}else{// The submitted data is valid, so process itprocess_form($input);}}else{// The form wasn't submitted, so displayshow_form();}functionshow_form($errors=array()){global$products;$defaults=array();// Start out with 0 as a defaultforeach($productsas$code=>$label){$defaults["quantity_$code"]=0;}// If quantities are in the session, use thoseif(isset($_SESSION['quantities'])){foreach($_SESSION['quantities']as$field=>$quantity){$defaults[$field]=$quantity;}}$form=newFormHelper($defaults);// All the HTML and form display is in a separate file for clarityinclude'order-form.php';}functionvalidate_form(){global$products;$input=array();$errors=array();// For each quantity box, make sure the value is// a valid integer >= 0foreach($productsas$code=>$name){$field="quantity_$code";$input[$field]=filter_input(INPUT_POST,$field,FILTER_VALIDATE_INT,['options'=>['min_range'=>0]]);if(is_null($input[$field])||($input[$field]===false)){$errors[]="Please enter a valid quantity for$name.";}}returnarray($errors,$input);}functionprocess_form($input){$_SESSION['quantities']=$input;"Thank you for your order.";}
The code relies on the FormHelper.php file discussed in Chapter 7. The order-form.php file referenced, which displays the form HTML, contains:
<formmethod="POST"action="<?=$form->encode($_SERVER['PHP_SELF']) ?>"><table><?phpif($errors){?><tr><td>You need to correct the following errors:</td><td><ul><?phpforeach($errorsas$error){?><li><?=$form->encode($error)?></li><?php}?></ul></td><?php}?><tr><th>Product</th><td>Quantity</td></tr><?phpforeach($productsas$code=>$name){?><tr><td><?=htmlentities($name)?>:</td><td><?=$form->input('text',['name'=>"quantity_$code"])?></td></tr><?php}?><tr><td colspan="2"align="center"><?=$form->input('submit',['value'=>'Order'])?></td></tr></table></form>
The checkout page:
session_start();// The same products from the order page$products=['cuke'=>'Braised Sea Cucumber','stomach'=>"Sauteed Pig's Stomach",'tripe'=>'Sauteed Tripe with Wine Sauce','taro'=>'Stewed Pork with Taro','giblets'=>'Baked Giblets with Salt','abalone'=>'Abalone with Marrow and Duck Feet'];// Simplified main page logic without form validationif($_SERVER['REQUEST_METHOD']=='POST'){process_form();}else{// The form wasn't submitted, so displayshow_form();}functionshow_form(){global$products;// The "form" is just a single submit button, so we won't use// FormHelper and just inline all the HTML hereif(isset($_SESSION['quantities'])&&(count($_SESSION['quantities'])>0)){"<p>Your order:</p><ul>";foreach($_SESSION['quantities']as$field=>$amount){list($junk,$code)=explode('_',$field);$product=$products[$code];"<li>$amount$product</li>";}"</ul>";'<form method="POST" action='.htmlentities($_SERVER['PHP_SELF']).'>';'<input type="submit" value="Check Out" />';'</form>';}else{"<p>You don't have a saved order.</p>";}// This assumes the order form page is saved as "order.php"'<a href="order.php">Return to Order page</a>';}functionprocess_form(){// This removes the data from the sessionunset($_SESSION['quantities']);"<p>Thanks for your order.</p>";}
$json=file_get_contents("http://php.net/releases/?json");if($json===false){"Can't retrieve feed.";}else{$feed=json_decode($json,true);// $feed is an array whose top-level keys are major release// numbers. First we need to pick the biggest one.$major_numbers=array_keys($feed);rsort($major_numbers);$biggest_major_number=$major_numbers[0];// The "version" element in the array under the major number// key is the latest release for that major version number$version=$feed[$biggest_major_number]['version'];"The latest version of PHP released is$version.";}
$c=curl_init("http://php.net/releases/?json");curl_setopt($c,CURLOPT_RETURNTRANSFER,true);$json=curl_exec($c);if($json===false){"Can't retrieve feed.";}else{$feed=json_decode($json,true);// $feed is an array whose top-level keys are major release// numbers. First we need to pick the biggest one.$major_numbers=array_keys($feed);rsort($major_numbers);$biggest_major_number=$major_numbers[0];// The "version" element in the array under the major number// key is the latest release for that major version number$version=$feed[$biggest_major_number]['version'];"The latest version of PHP released is$version.";}
// Seconds from Jan 1, 1970 until now$now=time();setcookie('last_access',$now);if(isset($_COOKIE['last_access'])){// To create a DateTime from a seconds-since-1970 value,// prefix it with @.$d=newDateTime('@'.$_COOKIE['last_access']);$msg='<p>You last visited this page at '.$d->format('g:i a').' on '.$d->format('F j, Y').'</p>';}else{$msg='<p>This is your first visit to this page.</p>';}$msg;
$url='https://api.github.com/gists';$data=['public'=>true,'description'=>"This program a gist of itself.",// As the API docs say:// The keys in the files object are the string filename,// and the value is another object with a key of content// and a value of the file contents.'files'=>[basename(__FILE__)=>['content'=>file_get_contents(__FILE__)]]];$c=curl_init($url);curl_setopt($c,CURLOPT_RETURNTRANSFER,true);curl_setopt($c,CURLOPT_POST,true);curl_setopt($c,CURLOPT_HTTPHEADER,array('Content-Type: application/json'));curl_setopt($c,CURLOPT_POSTFIELDS,json_encode($data));curl_setopt($c,CURLOPT_USERAGENT,'learning-php-7/exercise');$response=curl_exec($c);if($response===false){"Couldn't make request.";}else{$info=curl_getinfo($c);if($info['http_code']!=201){"Couldn't create gist, got{$info['http_code']}\n";$response;}else{$body=json_decode($response);"Created gist at{$body->html_url}\n";}}
The keyword global should not be in line 5, so the parse error should report that unexpected keyword. The actual parse error is:
PHP Parse error: syntax error, unexpected 'global' (T_GLOBAL) in debugging-12.php on line 5
To make the program run properly, change the line print global $name; to print $GLOBALS['name'];. Or, you can add global name; as the first line of the function and then change print global $name; to print $name;.
functionvalidate_form(){$input=array();$errors=array();// turn on output bufferingob_start();// dump all the submitted datavar_dump($_POST);// capture the generated "output"$output=ob_get_contents();// turn off output bufferingob_end_clean();// send the variable dump to the error logerror_log($output);// op is required$input['op']=$GLOBALS['ops'][$_POST['op']]??'';if(!in_array($input['op'],$GLOBALS['ops'])){$errors[]='Please select a valid operation.';}// num1 and num2 must be numbers$input['num1']=filter_input(INPUT_POST,'num1',FILTER_VALIDATE_FLOAT);if(is_null($input['num1'])||($input['num1']===false)){$errors[]='Please enter a valid first number.';}$input['num2']=filter_input(INPUT_POST,'num2',FILTER_VALIDATE_FLOAT);if(is_null($input['num2'])||($input['num2']===false)){$errors[]='Please enter a valid second number.';}// can't divide by zeroif(($input['op']=='/')&&($input['num2']==0)){$errors[]='Division by zero is not allowed.';}returnarray($errors,$input);}
At the top of the program, this code defines an exception handler and sets it up to be called on unhandled exceptions:
functionexceptionHandler($ex){// Log the specifics to the error logerror_log("ERROR: ".$ex->getMessage());// Print something less specific for users to see// and exitdie("<p>Sorry, something went wrong.</p>");}set_exception_handler('exceptionHandler');
Then the try/catch blocks can be removed from the two places they are used (once around creating the PDO object and once in process_form()) because the exceptions will be handled by the exception handler.
:: to : in the DSN.catch ($e) to catch (Exception $e).$row['dish_id']] to $row['dish_id'] as the key to look up in the $dish_names array.** to * in the SQL query.= to ==.%f to %s—$customer['phone'] is a string.$customer['favorite_dish_id'] to $dish_names[$customer['favorite_dish_id']] so that the dish ID is translated into the name of the corresponding dish.} to match the opening { in line 22.The complete corrected program is:
<?php// Connect to the databasetry{$db=newPDO('sqlite:/tmp/restaurant.db');}catch(Exception$e){die("Can't connect: ".$e->getMessage());}// Set up exception error handling$db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);// Set up fetch mode: rows as arrays$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);// Get the array of dish names from the database$dish_names=array();$res=$db->query('SELECT dish_id,dish_name FROM dishes');foreach($res->fetchAll()as$row){$dish_names[$row['dish_id']]=$row['dish_name'];}$res=$db->query('SELECT * FROM customers ORDER BY phone DESC');$customers=$res->fetchAll();if(count($customers)==0){"No customers.";}else{'<table>';'<tr><th>ID</th><th>Name</th><th>Phone</th><th>Favorite Dish</th></tr>';foreach($customersas$customer){printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>%s</td></tr>\n",$customer['customer_id'],htmlentities($customer['customer_name']),$customer['phone'],$dish_names[$customer['favorite_dish_id']]);}'</table>';}?>
publicfunctiontestNameMustBeSubmitted(){$submitted=array('age'=>'15','price'=>'39.95');list($errors,$input)=validate_form($submitted);$this->assertContains('Your name is required.',$errors);$this->assertCount(1,$errors);}
include'FormHelper.php';classFormHelperTestextendsPHPUnit_Framework_TestCase{public$products=['cu&ke'=>'Braised <Sea> Cucumber','stomach'=>"Sauteed Pig's Stomach",'tripe'=>'Sauteed Tripe with Wine Sauce','taro'=>'Stewed Pork with Taro','giblets'=>'Baked Giblets with Salt','abalone'=>'Abalone with Marrow and Duck Feet'];public$stooges=['Larry','Moe','Curly','Shemp'];// This code gets run before each test. Putting it in// the special setUp() method is more concise than having// to repeat it in each test method.publicfunctionsetUp(){$_SERVER['REQUEST_METHOD']='GET';}publicfunctiontestAssociativeOptions(){$form=newFormHelper();$html=$form->select($this->products);$this->assertEquals($html,<<<_HTML_<select ><option value="cu&ke">Braised <Sea> Cucumber</option><option value="stomach">Sauteed Pig's Stomach</option><option value="tripe">Sauteed Tripe with Wine Sauce</option><option value="taro">Stewed Pork with Taro</option><option value="giblets">Baked Giblets with Salt</option><option value="abalone">Abalone with Marrow and Duck Feet</option></select>_HTML_);}publicfunctiontestNumericOptions(){$form=newFormHelper();$html=$form->select($this->stooges);$this->assertEquals($html,<<<_HTML_<select ><option value="0">Larry</option><option value="1">Moe</option><option value="2">Curly</option><option value="3">Shemp</option></select>_HTML_);}publicfunctiontestNoOptions(){$form=newFormHelper();$html=$form->select([]);$this->assertEquals('<select ></select>',$html);}publicfunctiontestBooleanTrueAttributes(){$form=newFormHelper();$html=$form->select([],['np'=>true]);$this->assertEquals('<select np></select>',$html);}publicfunctiontestBooleanFalseAttributes(){$form=newFormHelper();$html=$form->select([],['np'=>false,'onion'=>'red']);$this->assertEquals('<select onion="red"></select>',$html);}publicfunctiontestNonBooleanAttributes(){$form=newFormHelper();$html=$form->select([],['spaceship'=>'<=>']);$this->assertEquals('<select spaceship="<=>"></select>',$html);}publicfunctiontestMultipleAttribute(){$form=newFormHelper();$html=$form->select([],["name"=>"menu","q"=>1,"multiple"=>true]);$this->assertEquals('<select name="menu[]" q="1" multiple></select>',$html);}}
The additional test methods for FormHelperTest:
publicfunctiontestButtonNoTypeOK(){$form=newFormHelper();$html=$form->tag('button');$this->assertEquals('<button />',$html);}publicfunctiontestButtonTypeSubmitOK(){$form=newFormHelper();$html=$form->tag('button',['type'=>'submit']);$this->assertEquals('<button type="submit" />',$html);}publicfunctiontestButtonTypeResetOK(){$form=newFormHelper();$html=$form->tag('button',['type'=>'reset']);$this->assertEquals('<button type="reset" />',$html);}publicfunctiontestButtonTypeButtonOK(){$form=newFormHelper();$html=$form->tag('button',['type'=>'button']);$this->assertEquals('<button type="button" />',$html);}publicfunctiontestButtonTypeOtherFails(){$form=newFormHelper();// FormHelper should throw an InvalidArgumentException// when an invalid attribute is provided$this->setExpectedException('InvalidArgumentException');$html=$form->tag('button',['type'=>'other']);}
The necessary modifications for FormHelper that make the tests pass are:
// This code goes just after the "class FormHelper" declaration// This array expresses, for the specified elements,// what attribute names have what allowed valuesprotected$allowedAttributes=['button'=>['type'=>['submit','reset','button']]];// tag() is modified to pass $tag as the first argument to// $this->attributes()publicfunctiontag($tag,$attributes=array(),$isMultiple=false){return"<$tag{$this->attributes($tag,$attributes,$isMultiple)}/>";}// start() is also modified to pass $tag as the first argument to// $this->attributes()publicfunctionstart($tag,$attributes=array(),$isMultiple=false){// <select> and <textarea> tags don't get value attributes on them$valueAttribute=(!(($tag=='select')||($tag=='textarea')));$attrs=$this->attributes($tag,$attributes,$isMultiple,$valueAttribute);return"<$tag$attrs>";}// attributes() is modified to accept $tag as a first argument,// set up $attributeCheck if allowed attributes for the tag have// been defined in $this->allowedAttributes, and then, if allowed// attributes have been defined, see if the provided value is// allowed and throw an exception if notprotectedfunctionattributes($tag,$attributes,$isMultiple,$valueAttribute=true){$tmp=array();// If this tag could include a value attribute and it// has a name and there's an entry for the name// in the values array, then set a value attributeif($valueAttribute&&isset($attributes['name'])&&array_key_exists($attributes['name'],$this->values)){$attributes['value']=$this->values[$attributes['name']];}if(isset($this->allowedAttributes[$tag])){$attributeCheck=$this->allowedAttributes[$tag];}else{$attributeCheck=array();}foreach($attributesas$k=>$v){// Check if the attribute's value is allowedif(isset($attributeCheck[$k])&&(!in_array($v,$attributeCheck[$k]))){thrownewInvalidArgumentException("$vis not allowed as value for$k");}// True boolean value means boolean attributeif(is_bool($v)){if($v){$tmp[]=$this->encode($k);}}// Otherwise k=velse{$value=$this->encode($v);// If this is an element that might have multiple values,// tack [] onto its nameif($isMultiple&&($k=='name')){$value.='[]';}$tmp[]="$k=\"$value\"";}}returnimplode(' ',$tmp);}