text

When working with a lucene index using the Zend Framework's lucene search component you'll often in the course of the index's lifecycle want to update documents. This can prove tricky with the current implementation as there is no insitu update feature, you must first delete the old document and add a new one. The tricky part is locating the unique document you want to update. The 'old way' was as following:

// Retrieving documents with find() method using a query string
  $query = $idFieldName . ':' . $docId;
  $hits  = $index->find($query);
  foreach ($hits as $hit) {
      $title = $hit->title;
      $contents = $hit->contents;
  }

This proves _painfully_ slow, you're loading the full index in an attempt to find a unique document with an ID. Even worse is if your unique ID happens to be a string such as a url or path. Since ZF 1.5, the 'best practice' direction to perform this type of task is to use the Zend_Search_Lucene::termDocs() method:

$term = new Zend_Search_Lucene_Index_Term('/somepath/somewhere', 'path');
  $docIds = $index->termDocs($term);
  foreach ($docIds as $id) {
      $doc = $index->getDocument($id);
      $title = $doc->title;
      $contents = $doc->contents;
  }

Performance wise this proves much more efficient. However, unless you're careful at the indexing stage you may run into trouble when running termDocs() on a string value such as a URL or path as opposed to an integer ID. This is down to the field being added tokenized. This is the most common way fields are added and corresponds to:

$doc = new Zend_Search_Lucene_Document();
  $doc->addField(Zend_Search_Lucene_Field::Text('title', $title));

If you want to use termDocs on an identifying field you need to add the field as type Keyword:

$doc = new Zend_Search_Lucene_Document();
  $doc->addField(Zend_Search_Lucene_Field::Keyword('http://a.com/uri', 'uri'));

Keyword fields are not tokenized, and a term vector (which termDocs() requires) is stored, the distinction between the two field types is documented in Zend_Search_Lucene_Field's phpdocs:

Zend_Search_Lucene_Field::Text() constructs a String-valued Field that is tokenized and indexed, and is stored in the index, for return with hits. Useful for short text fields, like "title" or "subject". Term vector will not be stored for this field.

In contrast see:

Zend_Seach_Lucene_Field::Keyword() constructs a String-valued Field that is not tokenized, but is indexed and stored. Useful for non-text fields, e.g. date or url.

This caught me out a little bit until I dug around the source a little bit looking to see where termDocs was going wrong. Hopefully this helps save someone else some time, and hopefully Zend can update their documentation to draw other developers' attention to this quirk.

text

One of the nicer, and largely unheralded I think, features of PHP 5 is its comprehensive reflection API. Arguably one of the reasons it is largely unheralded is because its documentation is a bit average. One great little tidbit, and the motivation for the blog post, is the ReflectionClass->getMethods($filter=null) method takes an optional parameter 'filter'. In the online documentation there is scant mention of what values this $filter parameter can take. Luckily in the comments Will Mason chimed in with paydirt:

If you are looking for the long $filters for ReflectionClass::getMethods(), here they are. They took me a  long time to find. Found nothing in the docs, nor google. But of course, Reflection itself was the final solution, in the form of ReflectionExtension::export("Reflection").
// The missing long $filter values!!!
  ReflectionMethod::IS_STATIC;
  ReflectionMethod::IS_PUBLIC;
  ReflectionMethod::IS_PROTECTED;
  ReflectionMethod::IS_PRIVATE;
  ReflectionMethod::IS_ABSTRACT;
  ReflectionMethod::IS_FINAL;
  
  // Use them like this
  $r = new ReflectionClass("MyClass");
  // Print all public methods
  foreach ($r->getMethods(ReflectionMethod::IS_PUBLIC) as $m) {
      echo $m->__toString();
  }
  

Another example, this time one of my own, is one that I found myself writing while working with the Zend Framework's Zend_Controller implementation:

/**
   * @param   String $controller_class
   * @return   ArrayObject
   */
  private function getActionList($controller_class)
  {
      $reflection_class = new ReflectionClass($controller_class);
      $methods = $reflection_class->getMethods(ReflectionProperty::IS_PUBLIC);
      return new ArrayObject($methods);
  }
  

Like many platforms, in PHP it seems documentation is no replacement for digging around the source itself.

Tags: php reflection
text

Some functions (fgetcsv, fputcsv for example) require a stream handle to work with. Similarly you have methods within zend_pdf to expect to read and write image data from a stream.

This can be inconvenient at times when you already have the data sitting in a variable. A way of getting around the need to worry about physically creating a file is to use the memory stream type.

PHP supports a number of input / output streams ranging from the usual stdin, stderr, stdout to memory, temp and filter.

See http://php.net/manual/en/wrappers.php.php for more information on these.

But looking at the memory type, it's very easy to use. Simply $fh = fopen('php://memory', 'wb+'); and you can use the usual file functions you would typically associate with an ondisk file.

You can fread, fwrite, file_get_contents on the memory stream or push it out over the network using the tcp streams PHP offers. PHP streams are a powerful and often underutilised aspect of the language.

Tags: php streams