root/trunk/samples/xsl/askaguru.xml

Revision 1, 22.3 kB (checked in by george, 10 years ago)

initial import

Line 
1 <?xml version="1.0" encoding="iso-8859-1"?>
2 <!DOCTYPE article SYSTEM "{DTD_PATH}" [
3
4 ]>
5 <!-- $Revision: 1.1.1.1 $ -->
6 <article>
7  <articleinfo>
8   <title>Ask a PHP Guru</title>
9   <subtitle>Any questions?</subtitle>
10  </articleinfo>
11
12  <epigraph>
13   <para>
14    In our magazine we give you, our readers, the opportunity to ask a real PHP
15    guru what you ever wanted to ask. This, of course, is not meant as a
16    support forum for general PHP questions; the PHP project has plenty of
17    mailinglists to deal with those kind of questions. The most interesting
18    questions will be published in the magazine, along with the written answer
19    of one of our gurus: Ilia Alshanetsky, Stig S. Bakken, Marcus Börger,
20    Stefan Esser, Wez Furlong, Sterling Hughes, Georg Richter, Zeev Suraski and
21    Andrei Zmievski.  We rather like to see conceptual questions and not
22    practical ones as they tend to be more interesting for other readers as
23    well.
24   </para>
25  </epigraph>
26
27  <section id="q1">
28   <title>Question 1: Threading</title>
29   <para>
30    <literal>Q: Is there a way to do a form of threading in PHP?</literal>
31   </para>
32
33   <para>
34    <literal>Say for instance you write a PHP application to monitor a service
35    on a number of servers, it would be nice to be able query a number of
36    servers at the same time rather then query them one-by-one.</literal>
37   </para>
38
39   <para>
40    A: People often assume that you need to fork or spawn threads whenever you
41    need to do several things at the same time - and when they realize that PHP
42    doesn't support threading they move on to something less nice, like perl.
43   </para>
44
45   <para>
46    The good news is that in the majority of cases you *don't* need to fork or
47    thread at all, and that you will often get much better performance for not
48    forking/threading in the first place.
49   </para>
50
51   <para>
52    Say you need to check up on web servers running on a number of hosts to
53    make sure that they are still responding to the outside world.  You might
54    write a script like in Listing 1.
55   </para>
56
57   <para>
58    <example>
59     <programlisting><![CDATA[<?php
60 $hosts = array("host1.sample.com", "host2.sample.com", "host3.sample.com");
61 $timeout = 15;
62 $status = array();
63
64 foreach ($hosts as $host) {
65  $errno = 0;
66  $errstr = "";
67  
68  $s = fsockopen($host, 80, $errno, $errstr, $timeout);
69  
70  if ($s) {
71   $status[$host] = "Connected\n";
72   fwrite($s, "HEAD / HTTP/1.0\r\nHost: $host\r\n\r\n");
73   do {
74    $data = fread($s, 8192);
75    if (strlen($data) == 0) {
76     break;
77    }
78    $status[$host] .= $data;
79   } while (true);
80   fclose($s);
81  } else {
82   $status[$host] = "Connection failed: $errno $errstr\n";
83  }
84 }
85
86 print_r($status);
87 ?>]]></programlisting>
88    </example>
89   </para>
90
91   <para>
92    This works fine, but since <function>fsockopen</function> doesn't return until it has resolved
93    the hostname and made a successful connection (or waited up to $timeout
94    seconds), extending this script to monitor a larger number of hosts makes
95    it slow to complete.
96   </para>
97
98   <para>
99    There is no reason why we have to do it sequentially; we can make
100    asynchronous connections - that is, connections where we don't have to wait
101    for fsockopen to return an opened connection.  PHP will still need to
102    resolve the hostname (so its better to use IP addresses), but will return
103    as soon as it has started to open the connection, so that we can move on to
104    the next host.
105   </para>
106
107   <para>
108    There are two ways to achieve this; in PHP 5, you can use the new
109    <function>stream_socket_client</function> function as a drop-in replacement
110    for <function>fsockopen</function>.  In earlier versions of PHP, you need
111    to get your hands dirty and use the sockets extension. Listing 2 shows how
112    to do it in PHP 5.
113   </para>
114
115   <para>
116    <example>
117     <programlisting><![CDATA[<?php
118 $hosts = array("host1.sample.com", "host2.sample.com", "host3.sample.com");
119 $timeout = 15;
120 $status = array();
121 $sockets = array();
122
123 /* Initiate connections to all the hosts simultaneously */
124 foreach ($hosts as $id => $host) {
125  $s = stream_socket_client("$host:80", $errno, $errstr, $timeout,
126   STREAM_CLIENT_ASYNC_CONNECT);
127
128  if ($s) {
129   $sockets[$id] = $s;
130   $status[$id] = "in progress";
131  } else {
132   $status[$id] = "failed, $errno $errstr";
133  }
134 }
135
136 /* Now, wait for the results to come back in */
137 while (count($sockets)) {
138  $read = $write = $sockets;
139  /* This is the magic function - explained below */
140  $n = stream_select($read, $write, $e = null, $timeout);
141
142  if ($n > 0) {
143   /* readable sockets either have data for us, or are failed
144    * connection attempts */
145   foreach ($read as $r) {
146    $id = array_search($r, $sockets);
147    $data = fread($r, 8192);
148    if (strlen($data) == 0) {
149     if ($status[$id] == "in progress") {
150      $status[$id] = "failed to connect";
151     }
152     fclose($r);
153     unset($sockets[$id]);
154    } else {
155     $status[$id] .= $data;
156    }
157   }
158   /* writeable sockets can accept an HTTP request */
159   foreach ($write as $w) {
160    $id = array_search($w, $sockets);
161    fwrite($w, "HEAD / HTTP/1.0\r\nHost: "
162     . $hosts[$id] .  "\r\n\r\n");
163    $status[$id] = "waiting for response";
164   }
165  } else {
166   /* timed out waiting; assume that all hosts associated
167    * with $sockets are faulty */
168   foreach ($sockets as $id => $s) {
169    $status[$id] = "timed out " . $status[$id];
170   }
171   break;
172  }
173 }
174
175 foreach ($hosts as $id => $host) {
176  echo "Host: $host\n";
177  echo "Status: " . $status[$id] . "\n\n";
178 }
179 ?>]]></programlisting>
180    </example>
181   </para>
182
183   <para>
184    We are using <function>stream_select</function> to wait for events on the sockets that we
185    opened.  <function>stream_select</function> calls the system select(2) function and it works
186    like this: The first three parameters are arrays of streams that you want
187    to work with; you can wait for reading, writing and exceptional events
188    (parameters one, two and three respectively).  <function>stream_select</function> will wait up
189    to $timeout seconds for an event to occur - when it does, it will modify
190    the arrays you passed in to contain the sockets that have met your
191    criteria.
192   </para>
193   <para>
194    Now, using PHP 4.1.0 and later, if you have compiled in support for
195    ext/sockets, you can use the same script as above, but you need to replace
196    the regular streams/filesystem function calls with their equivalents from
197    ext/sockets.  The major difference though is in how we open the connection;
198    instead of <function>stream_socket_client</function>, you need to use the function in Listing
199    3.
200   </para>
201
202   <para>
203    <example>
204     <programlisting><![CDATA[<?php
205 // This value is correct for Linux, other systems have other values
206 define('EINPROGRESS', 115);
207 function non_blocking_connect($host, $port, &$errno, &$errstr, $timeout) {
208  $ip = gethostbyname($host);
209  $s = socket_create(AF_INET, SOCK_STREAM, 0);
210  if (socket_set_nonblock($s)) {
211   $r = @socket_connect($s, $ip, $port);
212   if ($r || socket_last_error() == EINPROGRESS) {
213    $errno = EINPROGRESS;
214    return $s;
215   }
216  }
217  $errno = socket_last_error($s);
218  $errstr = socket_strerror($errno);
219  socket_close($s);
220  return false;
221 }
222 ?>]]></programlisting>
223    </example>
224   </para>
225
226   <para>
227    Now, replace <function>stream_select</function> with <function>socket_select</function>, <function>fread</function> with
228    <function>socket_read</function>, <function>fwrite</function> with <function>socket_write</function> and <function>fclose</function> with
229    <function>socket_close</function> and you are ready to run the script.
230   </para>
231   <para>
232    The advantage of the PHP 5 approach is that you can use <function>stream_select</function> to
233    wait on (almost!) any kind of stream - you can wait for keyboard input from
234    the terminal by including STDIN in your read array for example, and you can
235    also wait for data from pipes created by the <function>proc_open</function> function.
236   </para>
237   <para>
238    Although PHP 5 is not ready for prime-time hosting, you should find it
239    stable enough to use the CLI version in this scenario - feel free to try
240    out a snapshot!  If you are not brave enough to run with PHP 5, go with PHP
241    4.3.2 and use ext/sockets.
242   </para>
243   <para>
244    If you want PHP 4.3.2 *and* want to use the native streams approach, I have
245    prepared a patch that allows fsockopen to work asynchronously.  The patch
246    is unsupported and won't be in an official PHP release, however, I've
247    provided a wrapper that implements the <function>stream_socket_client</function> function
248    along with the patch, so that your code will be forwards compatible with
249    PHP 5.
250   </para>
251   <para>
252    <literal>-- Wez Furlong</literal>
253   </para>
254
255   <glosslist>
256    <glossentry>
257     <glossterm>http://www.php.net/stream_select</glossterm>
258     <glossdef><para>documentation for <function>stream_select</function></para></glossdef>
259    </glossentry>
260
261    <glossentry>
262     <glossterm>http://www.php.net/socket_select</glossterm>
263     <glossdef><para>documentation for <function>socket_select</function></para></glossdef>
264    </glossentry>
265
266    <glossentry>
267     <glossterm>http://www.php.net/~wez/guru-multiplexing.tgz</glossterm>
268     <glossdef><para>patch for 4.3.2 and script to emulate
269     stream_socket_client.</para></glossdef>
270    </glossentry>
271   </glosslist>
272
273  </section>
274
275  <section id="q2">
276   <title>Question 2: PEAR on MacOSX</title>
277
278   <para>
279    <literal>Q: I've read articles about it, see a lot of comments on PEAR, so
280    it would be nice to test it myself.  PEAR is installed (sort of) with PHP
281    4.3.0, but how do I get it to work, to see the web-based installer
282    etc.?</literal>
283   </para>
284   <para>
285    <literal>I can't find any installing information on pear.php.net for MacOS
286    X.  I don't seem to find the logic to get it to work... it would be nice if
287    anyone can help me find the logic behind it and get the whole thing to
288    work.</literal>
289   </para>
290   <para>
291    A: With the 4.3.0 releases, Macintosh users can now work with their
292    favourite scripting langage as any other Linux, *BSD or Windows users. Here
293    I'll explain how to setup PEAR on your box and then enjoy the powerfull
294    PEAR package manager.
295   </para>
296   <para>
297    First, you need a working PHP installation using PHP 4.2.0 or recent
298    releases. I do not cover the installation of PHP here, please read the
299    usefull tutorials in the links sections or uses the binaries available on
300    <literal>entropy.ch</literal>. We recommand the use of the CLI (PHP
301    configure option <literal>--enable-cli</literal>) interface to install PEAR
302    in the console mode. The CGI version works well too (available with the
303    binaries). To use the web installer, you will need a working Apache/PHP
304    installation.
305   </para>
306   <para>
307    To install PEAR packages you need the installer. This tool includes
308    everything you need to install or upgrade a pear packages or create your
309    own package.
310   </para>
311   <para>
312    A crossplatform setup script is available through
313    <literal>http://go-pear.org</literal>.  Grabing and executing is an easy
314    task:
315   </para>
316   <para>
317    <screen>
318 % curl http://go-pear.org | php
319    </screen>
320   </para>
321   <para>
322    Or you may always download it from your favourite browser save the file as
323    <literal>go-pear.php</literal> and launch it using:
324   </para>
325   <para>
326    <screen>
327 CLI:
328 % php go-pear.php
329 CGI:
330 % php -q go-pear.php
331    </screen>
332   </para>
333
334   <para>
335    At this point, the following steps are explained in the setup script
336    itself. I recommand to carefully read the possible warning or notices
337    during the installation process. These texts are usefull to solve any
338    problems during this phase.
339   </para>
340
341   <para>
342    To do change permissions on the default PHP installation folder, you can
343    choose the installation folder as follows:
344   </para>
345
346   <para>
347    <screen>
348 Installation prefix: /usr/local
349 Binaries folders: $prefix/bin
350 PHP Code folders: $prefix/share/pear
351    </screen>
352   </para>
353   <para>
354    Verify everything works well and your pear command is in your path by
355    entering this command:
356   </para>
357   <para>
358    <screen>
359 % pear -V
360 PEAR Version: 1.1
361 PHP Version: 4.3.2
362 Zend Engine Version: 1.3.0
363    </screen>
364   </para>
365   <para>
366    You may need to update your <literal>include_path</literal> according to
367    your PEAR installation by editing you <literal>php.ini</literal>:
368   </para>
369   <para>
370    <screen>
371 % bbedit -c /usr/local/lib/php.ini
372    </screen>
373   </para>
374
375   <para>
376 Modify or add the following line:
377   </para>
378   <para>
379    <screen>
380 include_path = ".:/usr/local/share/pear";
381    </screen>
382   </para>
383
384   <para>
385    Now you are ready to use PEAR. You can list the installed packages with:
386   </para>
387   <para>
388    <screen>
389 % pear list
390 Installed packages:
391 ===================
392 Package              Version State 
393 Archive_Tar          1.0     stable
394 Auth                 1.2.0   stable
395 Benchmark            1.2     stable
396 Cache_Lite           1.1     stable
397 Config               1.5     stable
398 Install a package (i.e. Text_Statistics)
399 % pear install Text_Statistics
400 downloading Text_Statistics-1.0.tgz ...
401 ...done: 0 bytes
402 install ok: Text_Statistics 1.0
403    </screen>
404   </para>
405
406   <para>
407    <figure>
408     <title>pear list-all</title>
409     <graphic fileref="pear-list-all.png"/>
410    </figure>
411   </para>
412
413   <para>
414    The web installer is still in an alpha state, but should be usefull. The
415    first step is to get the <literal>go-pear</literal> script using your
416    browser (using "save target as") and save it somewhere under your document
417    root directory. The easiest way is to create a new directory for pear and
418    put the files there.
419   </para>
420   <para>
421    Once <literal>go-pear.php</literal> has been saved, make sure PHP has the
422    write permissions in the installation folder. Then you can launch the
423    installation from your browser
424    (<literal>http://localhost/pear/go-pear.php</literal>).
425   </para>
426   <para>
427    The setup steps are self explained in the different screen of the
428    installer. After having running go-pear, you get a link to the web
429    installer itself. I suggest to bookmark this link. As we did for the CLI
430    installer, you may need to update the include_path to point to your PEAR
431    installation directory.
432   </para>
433   <para>
434    To skip wide usage of the PEAR web frontend, I strongly recommand to
435    protect the PEAR directory and the go-pear script with a password, i.e.
436    using an <literal>.htaccess</literal> file.
437   </para>
438   <para>
439    The usage of the PEAR web Frontend is very easy, so I don't explain how to
440    click on the different buttons :).
441   </para>
442   <para>
443    <literal>-- Pierre-Alain Joye</literal>
444   </para>
445
446   <para>
447    <figure>
448     <title>The setup script's installing packages</title>
449     <graphic fileref="pear-webinstaller-packages-mozilla.png"/>
450    </figure>
451   </para>
452
453   <para>
454    <figure>
455     <title>The web frontend in action</title>
456     <graphic fileref="pear-webinstaller-install-on-safari.png"/>
457    </figure>
458   </para>
459
460   <glosslist>
461    <glossentry>
462     <glossterm>PHP 4.3.0 and Mac OS X</glossterm>
463     <glossdef><para>http://www.onlamp.com/pub/a/php/2003/01/17/phpcookbook.html</para></glossdef>
464    </glossentry>
465    <glossentry>
466     <glossterm>PHP binaries for Mac OS X</glossterm>
467     <glossdef><para>http://www.entropy.ch/software/macosx/php/</para></glossdef>
468    </glossentry>
469    <glossentry>
470     <glossterm>Misc. articles about php and Mac OS X</glossterm>
471     <glossdef><para>http://www.phpmac.com/</para></glossdef>
472    </glossentry>
473   </glosslist>
474  </section>
475
476  <section id="q3">
477   <title>Question 3: MySQL login problems</title>
478
479   <para>
480    <literal>Q: Since we use MySQL 4.1 users of our website aren't able to
481    login anymore. SELECT id from c_user WHERE login='name' and
482    passwd=password('passwd') always returns an empty result set.  This also
483    happens when I use the external MySQL 4.1 library instead of the bundled
484    lib.  Do we need to switch from ext/mysql to ext/mysqli to solve this
485    problem?</literal>
486   </para>
487   <para>
488    A: As described in the manual the <function>password</function> function
489    should only be used for operations on the mysql user table. For encrypting
490    passwords in other tables, you should use <function>md5</function> or
491    <function>sha</function>.  From MySQL 4.0 to 4.1 the
492    <function>password</function>-function changed: It has now a stronger and
493    better encryption: The encrypted string is salted, and it's length changed
494    from 16 to 45 chars. Fortunately MySQL AB developers implemented a
495    workaround: the function <function>old_password</function> which supports
496    the old encryption.
497   </para>
498   <para>
499    <screen><![CDATA[
500         mysql> select password("php magazine");
501         +-----------------------------------------------+
502         | password("php magazine")                      |
503         +-----------------------------------------------+
504         | *9fba5e64a7c11ce87d229c43cdb0f0ab87f7886dd698 |
505         +-----------------------------------------------+
506         1 row in set (0.00 sec)
507        
508         mysql> select old_password("php magazine");
509         +------------------------------+
510         | old_password("php magazine") |
511         +------------------------------+
512         | 001b8f92205d4470             |
513         +------------------------------+
514         1 row in set (0.00 sec)]]></screen>
515   </para>
516   <para>
517    Because there is no function to decrypt a stored password, it's only
518    possible to change the password to an md5 or sha encrypted value, with the
519    interaction of the user during the login process.
520   </para>
521   <para>
522    <literal>-- Georg Richter</literal>
523   </para>
524  </section>
525
526  <section id="q4">
527   <title>Question 4: MySQL random rows</title>
528
529   <para>
530    <literal>Q: Last year we launched a single community portal which has over
531    100.000 active members.  Since we implemented a little box which shows five
532    random entries with user ads on the entry page we noticed a heavy load and
533    speed loss on our system.  As suggested in the MySQL manual we use the
534    order by <function>rand</function> method in combination with
535    LIMIT</literal>.
536   </para>
537   <para>
538    A: Order by <function>rand</function> might be an useful feature for
539    smaller tables. Depending on the selected columns it requires a full index-
540    or tablescan which is very expensive: For selecting five rows from 100.000
541    rows, MySQL has to read all 100.000 rows and sort it by random. The best
542    way to select random entries from large tables is to use the MySQL Handler
543    (this requires a MySQL-Version &gt; 4.0.2).
544   </para>
545   <para>
546    First determine the number of rows in your ads table:
547   </para>
548   <para>
549    <screen><![CDATA[
550         if ($result = mysql_unbuffered_query("SELECT COUNT(*) FROM ads")) {
551            $row = mysql_fetch_row($result);
552            $total = $row[0];
553            mysql_free_result($result);
554         }]]></screen>
555   </para>
556   <para>
557    On client side you now have to create 5 diffrent random values between 1
558    and <literal>$total</literal> (be sure to check possible duplicate values).
559   </para>
560   <para>
561    Hopefully lot of users found a partner via your site and removed or
562    deactivated their ads. Otherwise it would be easy now to select some random
563    rows, cause there are no gaps in your (autoincrement-) id's:
564   </para>
565   <para>
566    <screen><![CDATA[$result = mysql_query("SELECT columns from ads WHERE id in (....)");]]></screen>
567   </para>
568   <para>
569    When you have some gaps in your id, this solution will not work of cause.
570    In this case <literal>HANDLER</literal> is a good and fast way.
571    <literal>HANDLER</literal> provides a low-level interface for table
572    handlers which bypasses the optimzer and accesses table content directly:
573   </para>
574   <para>
575    <screen><![CDATA[mysql_query("HANDLER cuser open");]]></screen>
576   </para>
577   <para>
578    This opens the handler for read access.  Now you can fetch the diffrent
579    rows with your calculated random values:
580   </para>
581   <para>
582    <screen><![CDATA[
583         $result = mysql_query("HANDLER cuser READ first LIMIT $rand_1, 1");
584         /* fetch rows from result set */
585         ...
586
587         $result = mysql_query("HANDLER cuser READ first LIMIT $rand_2, 1");
588         ..]]></screen>
589   </para>
590   <para>
591    After fetching all rows you have to close the handler:
592   </para>
593   <para>
594    <screen><![CDATA[mysql_query("HANDLER cuser CLOSE");]]></screen>
595   </para>
596   <para>
597    <literal>-- Georg Richter</literal>
598   </para>
599  </section>
600
601  <section id="q5">
602   <title>Question 5: Syntax highlighting</title>
603   <para>
604    <literal>Q: I have wanted to hack up a cross-referencer for some time. Sat
605    down to play with Listings 1 and 2  of 'Under the Hood (3.2003)' but
606    rapidly realised that there is no apparent linkage between the available
607    functions (man App CI) and code/printout line numbers. (Yet there is at the
608    compile/debug phase!?)</literal>
609   </para>
610   <para>
611    <literal>Looked at a few of the pretty-printer scripts (syntax
612    highlighter) but none of them (at least of the two/three I examined) seems
613    to use the technique/token_get_all().
614   </literal>
615   </para>
616   <para>
617    <literal>Short of merging the source code with the tokens how can the array of
618    tokens be related to code line numbers as required for the
619    tools/applications of the technique mentioned above/in the article?
620    </literal>
621   </para>
622   <para>
623    A: The <literal>T_WHITESPACE</literal> as shown on page 70 includes the
624    newlines, but that was not printed in the magazine for obvious reasons. The
625    token is matched by the engine on <literal>[ \n\r\t]+</literal> which
626    includes the new line characters. By examinating the
627    <literal>T_WHITESPACE</literal> you will be able to deterine the line
628    number. (Of course you need to check all other tokens that can contain
629    whitespace too, like <literal>T_CLOSE_TAG</literal>,
630    <literal>T_ENCAPSED_AND_WHITESPACE</literal>,
631    <literal>T_OPEN_TAG</literal>, <literal>T_CLOSE_TAG</literal>,
632    <literal>T_COMMENT</literal>,
633    <literal>T_CONSTANT_ENCAPSED_STRING</literal>, <literal>T_STRING</literal>,
634    <literal>T_INLINE_HTML</literal>, <literal>T_START_HEREDOC</literal> and
635    <literal>T_END_HEREDOC</literal>. As you see this is quite a list and I
636    might have even missed some cases. The best way to do it is by using the
637    <function>highlight_string</function> in PHP. The article <literal>Error
638    handling</literal> article in this issue gives you some nice example on how
639    to use syntax highlighting.
640   </para>
641   <para>
642    <literal>-- Derick Rethans</literal>
643   </para>
644
645   <glosslist>
646    <glossentry>
647     <glossterm>http://www.php.net/highlight-string</glossterm>
648     <glossdef><para>documentation for <function>highlight_string</function></para></glossdef>
649    </glossentry>
650   </glosslist>
651  </section>
652 </article>
653 <!-- keep this comment at the end of the file
654 vim600: syn=xml fen fdm=syntax fdl=4 si
655 vim: et tw=78 syn=sgml
656 vi: ts=1 sw=1
657 -->
Note: See TracBrowser for help on using the browser.