1. Over-zealous Ebay ads

    Well, definitely not PHP related, but...

    Anyone else noticed that Ebay's affiliates have been a little over-zealous in their ads, lately?

    Babies for sale... [img]http://blog.phpdoc.info/uploads/adsgonewrong.png[/img]

    Google for "bombs"... [img]http://blog.phpdoc.info/uploads/adsgonewrong2.png[/img]

    (my wife pointed out the "babies" one)

    Weird stuff.. (-:

  2. Fun with the tokenizer...

    I was reminded, this past week, of how cool the tokenizer is.

    One of the guys who works in the same office as I do had what seemed to be a simple problem: he had a php file that contained ~50 functions, and wanted to summarize the API without parsing through the file, manually, and cutting out the function declarations.

    We introduced him to in-line phpdoc blocks (he works (as a Jr.-level PHP developer) in the same office, but for a different company, so he doesn't have to follow our coding standards, but I digress..), but the 50-function library in question didn't have docblocks.

    Sure, he could (and did) pull up a list function NAMES with get_defined_functions (I assume by using array_diff against a before-and-after capture), but this didn't give him the argument names, or even the number of arguments for a given function, so I broke out some old tokenizer code I'd written.

    In case you aren't familiar with the tokenizer, the PHP manual defines it as:

    “[an interface to let you write] your own PHP source analyzing or modification tools without having to deal with the language specification at the lexical level.”

    The extension (which has been part of the PHP core distribution since 4.3.0) consists only of two functions: token_get_all and token_name, and a boatload of constants.

    Enough babble, though, let's get to the meat. I pulled out this code I'd written for PEARClops (on EFNet #PEAR) that parses PHP source files and figures out what classes, functions/methods and associated parameters are included.

    [php] $tokens[$i][1], 'class' => $currClass, ); } else { $thisFunc['params'][] = array( 'byRef' => $nextByRef, 'name' => $tokens[$i][1], ); $nextByRef = FALSE; } } elseif ($tokens[$i] == '&') { $nextByRef = TRUE; } elseif ($tokens[$i] == '=') { while (!in_array($tokens[++$i], array(')',','))) { if ($tokens[$i][0] != T_WHITESPACE) { break; } } $thisFunc['params'][count($thisFunc['params']) - 1]['default'] = $tokens[$i][1]; } } $funcs[] = $thisFunc; } elseif ($tokens[$i] == '{') { ++$classDepth; } elseif ($tokens[$i] == '}') { --$classDepth; }

    if ($classDepth == 0) { $currClass = ''; } }

    return $funcs; }

    function parse_protos($funcs) { $protos = array(); foreach ($funcs AS $funcData) { $proto = ''; if ($funcData['class']) { $proto .= $funcData['class']; $proto .= '::'; } $proto .= $funcData['name']; $proto .= '('; if ($funcData['params']) { $isFirst = TRUE; foreach ($funcData['params'] AS $param) { if ($isFirst) { $isFirst = FALSE; } else { $proto .= ', '; } if ($param['byRef']) { $proto .= '&'; } $proto .= $param['name']; } } $proto .= ")"; $protos[] = $proto; } return $protos; } echo "Functions in {$_SERVER['argv'][1]}:\n"; foreach (parse_protos(get_protos($_SERVER['argv'][1])) AS $proto) { echo " $proto\n"; } ?> [/php]

    Save it as "parse_funcs.php" (or whatever you like) and call it like so: php parse_funcs.php /path/to/php_file

    For instance: [code] sean@iconoclast:~/php/scripts$ php token_funcs_cli.php ~/php/cvs/Mail_Mime/mime.php Functions in /home/sean/php/cvs/Mail_Mime/mime.php: Mail_mime::Mail_mime($crlf) Mail_mime::__wakeup() Mail_mime::setTXTBody($data, $isfile, $append) Mail_mime::setHTMLBody($data, $isfile) Mail_mime::addHTMLImage($file, $c_type, $name, $isfilename) Mail_mime::addAttachment($file, $c_type, $name, $isfilename, $encoding) Mail_mime::_file2str(&$file_name) Mail_mime::_addTextPart(&$obj, $text) Mail_mime::_addHtmlPart(&$obj) Mail_mime::_addMixedPart() Mail_mime::_addAlternativePart(&$obj) Mail_mime::_addRelatedPart(&$obj) Mail_mime::_addHtmlImagePart(&$obj, $value) Mail_mime::_addAttachmentPart(&$obj, $value) Mail_mime::get(&$build_params) Mail_mime::headers(&$xtra_headers) Mail_mime::txtHeaders($xtra_headers) Mail_mime::setSubject($subject) Mail_mime::setFrom($email) Mail_mime::addCc($email) Mail_mime::addBcc($email) Mail_mime::_encodeHeaders($input) Mail_mime::_setEOL($eol) [/code]

    Not bad, huh?

    There are some not-so-obvious bugs (inheritance, mostly), but for a relatively short script, it does a pretty good job.

  3. PDO::firstImpressions()

    My pet project du jour has to do with PHP, commits and RSS. I'll talk about it here when it's ready, but in the mean time, here's a teaser.

    Part of what I'm doing is putting commit data into a database. Since I'm re-architecting my database schema and access methods, I figured that this would be the perfect opportunity to try PDO.

    (UPDATED below)

    Here are my first impressions.

    Let me say, first, that I think PDO has a LOT of potential. Even the work that has already been completed is wonderful, and it will push PHP forward into even heavier usage. I used to write CFML (please don't hold it against me), and one of the key benefits of Coldfusion was uniform database connectivity.

    Additionally, I've never been able to do cool things like use prepared queries in MySQL before, and while it's not technically possible to actually do this in (at least in the version I run -- see below), PDO's emulation layer is an incredible idea that I'm going to have a hard time living without at work (where we're still on PHP 4.. at least for the next couple months).

    In short: PDO is great. It will become an indispensible part of the PHP core distribution. Kudos to all involved.

    I'm running PHP 5.0.3 with php checked out from PECL CVS. The database I maintain on that box is MySQL, so I grabbed both PDO and PDO_MYSQL. I used the phpize method, and then stuck the extension=*.so directives in my php.ini.

    So far so good.

    The version of MySQL I have installed is that which is part of Debian stable: MySQL v3.23.

    Here's where the going got rough. I was (apparently) the first to try PDO on this version of MySQL. The first time I constructed a PDO object, an exception was thrown, which lead to PECL bug #3470.

    Wez has been not only instrumental in creating PDO, but also extremely helpful in diagnosing/confirming (or bogussing (-: ) the problems I've had.

    So, I commented out the offending line that caused the constructor to fail (and I see that this has been fixed in CVS, but I'm unable to test, currently (again, see below)), and continued working on my project.

    The prepared statements are REALLY nice. I can't say I've ever had such an enjoyable experience with MySQL access (except for DBDO).

    I ran into three more issues. The last of which leaves me unable to use PDO in its current state (in CVS).

    All in all, PDO is going to be great. Hopefully in time for PHP 5.1 (tentatively March 1st). I'd even suggest postponing 5.1 if PDO still has major bugs.

    PDO_MYSQL isn't what I'd consider production/release quality, currently, but these bugs seem like they'll be easily overcome. I think it needs more testers -- what I've found broken, I've reported, but some of this stuff I was quite surprised to find that I'm the first to discover the flaw.

    It seems that with PDO, MySQL was largely overlooked (the absence of a PDO_MYSQLI attests to this), and I'm certainly not one to extoll the virtues of MySQL, many PHP users know nothing else, and we'd be well-advised to make PDO_MYSQL as smooth as possible.

    ... anxiously awaiting a stable PDO and 5.1. Again, great work, guys.

    UPDATE: Following Wez' suggestion in bug #3549, (but also additionally actually removing the previously-instlaled pdo*.so files), I was able to get PDO working in its current state (from CVS).

  4. Schizophrenic Methods

    Occasionally, it is useful for a developer to determine if a method is being called statically (not in an object context -- Class::method() ), or "not statically" (in an object context -- $object->method()).

    This is normally (but incorrectly) done by checking $this:

    [php] class Foo { function bar() { echo "bar() called: " . (isset($this) ? 'non-statically' : 'statically'); } } [/php]

    Why the "but incorrectly", you might ask?

    A few weeks ago, I started maintaining PEAR::Mail_Mime -- it had a lot of reported bugs, and nobody was really taking care of the package. (I'm going to release 1.3.0RC1 within the next couple weeks)

    Anyway, without getting too far off-topic, one of the bugs was "Fatal error: Using $this when not in object context."

    Basically, the code was checking for $this->mailMimeDecode, but when called statically, $this was unset.

    My fix was to check if $this was set, but once committed, Jan Schneider sent me mail telling me that my patch would not work if the method was called statically from within another object.

    This hadn't even occurred to me, so I did some testing (and eventually updated the manual).

    Here's the scenario:

    [php] class A { function foo() { if (isset($this)) { echo '$this is defined ('; echo get_class($this); echo ")\n"; } else { echo "\$this is not defined.\n"; } } } class B { function bar() { A::foo(); } } $a = new A(); $a->foo(); A::foo(); $b = new B(); $b->bar(); B::bar(); [/php]

    The output:

    [code] $this is defined (a) $this is not defined. $this is defined (b) $this is not defined. [/code]

    As you can see (if you have the human parser module installed (-: ), $this is defined, and is the calling object, even when a method is called statically (but from the context of another object).

    So (and here's my point), how does a developer determine if a given method is called statically? Here's what I came up with (and is in Mail_Mime - CVS):

    [php] $isStatic = !(isset($this) && get_class($this) == __CLASS__); [/php]

    It seems a little hackish to be using __CLASS__, but nothing else came to mind, and it works in every test I came up with.

    Side note: When I stuck this stuff in the manual, its place in the oop4 docs is pretty good, but in the oop5 docs, I don't like that it's in The Basics but I don't know where else to put it. So, if anyone has a good suggestion, let me know.

  5. PHP Fun - Variable Arguments Be Reference?

    Earlier, this week, one of my co-workers was working on a personal project in which he wanted to use a function to set a variable number of parameters to zero.

    [php] [/php]

    His first impulse was to use func_get_args(). But this wasn't working for him. Turns out function_get_args() returns a COPY of the arguments, and not a reference. The manual didn't lean either way or the other, so I updated it.

    A few mails bounced around our internal developer's list. It seems that there's no non-hack way to do this. Here's what we came up with:

    Non-Solution #1

    [php] $v) { $array[$k] = 0; } } $a = 1; $b = 2; $c = 3; set_array_to_zero(array(&$a, &$b, &$c)); echo "$a $b $c"; // prints "0 0 0"; ?> [/php]

    This WORKS, and is probably the most "proper" way to do this, but the semantics violate his original requirements.

    A couple other non-semantic-repecting solutions were proposed (one using $obj = new StdClass;), but nothing that really worked the way he intended.

    Here's one that seems pretty close, on the surface: Non-Solution #4

    [php] [/php]

    Sure, it breaks semantics, again, but there's another major problem -- scope: Non-Solution #5

    [php] [/php]

    Since there's no way to operate on an intermediate scope (only local symbols and global symbols), at least in a functional context, this approach is a dead end.

    Finally, after sleeping on it, I came up with this:

    Non-Solution #16

    [php] [/php]

    This ALMOST works--I mean, it's REALLY close. The semantics are still a LITTLE off -- the $ I can live with. The @error_suppressor is also necessary, because it seems that it's not possible to pass default values to create_function(...) (try removing the @ -- you'll get (1024 - actual_number_of_arguments) error messages). There's also the issue of limiting the number of arguments to an arbitrary number. The way I see it, no developer should be working on > 1000 (actually, even that is entirely too many), anyway.

    So, without resorting to eval / writing to a file + include, this is the best solution we could come up with.

    Personally, I'd alter my requirements to go with the array-passing approach.