Index: src/java/org/apache/solr/request/PHPResponseWriter.java
===================================================================
--- src/java/org/apache/solr/request/PHPResponseWriter.java	(revision 0)
+++ src/java/org/apache/solr/request/PHPResponseWriter.java	(revision 0)
@@ -0,0 +1,105 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.request;
+
+import java.io.Writer;
+import java.io.IOException;
+
+import org.apache.solr.common.util.NamedList;
+
+public class PHPResponseWriter implements QueryResponseWriter {
+  static String CONTENT_TYPE_PHP_UTF8="text/x-php;charset=UTF-8";
+
+  public void init(NamedList n) {
+    /* NOOP */
+  }
+  
+ public void write(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) throws IOException {
+    PHPWriter w = new PHPWriter(writer, req, rsp);
+    w.writeResponse();
+  }
+
+  public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
+    return CONTENT_TYPE_TEXT_UTF8;
+  }
+}
+
+class PHPWriter extends JSONWriter {
+  public PHPWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) {
+    super(writer, req, rsp);
+  }
+  
+  @Override
+  public void writeNamedList(String name, NamedList val) throws IOException {
+    writeNamedListAsMapMangled(name,val);
+  }
+
+  @Override
+  public void writeMapOpener(int size) throws IOException {
+    writer.write("array(");
+  }
+
+  @Override
+  public void writeMapCloser() throws IOException {
+    writer.write(')');
+  }
+
+  @Override
+  public void writeArrayOpener(int size) throws IOException {
+    writer.write("array(");
+  }
+
+  @Override
+  public void writeArrayCloser() throws IOException {
+    writer.write(')');
+  }
+
+  @Override
+  public void writeNull(String name) throws IOException {
+    writer.write("null");
+  }
+
+  @Override
+  protected void writeKey(String fname, boolean needsEscaping) throws IOException {
+    writeStr(null, fname, needsEscaping);
+    writer.write("=>");
+  }
+
+  @Override
+  public void writeStr(String name, String val, boolean needsEscaping) throws IOException {
+    if (needsEscaping) {
+      StringBuilder sb = new StringBuilder(val.length());
+      sb.append('\'');
+      for (int i=0; i<val.length(); i++) {
+        char ch = val.charAt(i);
+        switch (ch) {
+          case '\'':
+          case '\\': sb.append('\\'); sb.append(ch); break;
+          default:
+            sb.append(ch);
+        }
+      }
+      sb.append('\'');
+      writer.append(sb);
+    } else {
+      writer.write('\'');
+      writer.write(val);
+      writer.write('\'');
+    }
+  }
+}
Index: src/java/org/apache/solr/request/PHPSerializedResponseWriter.java
===================================================================
--- src/java/org/apache/solr/request/PHPSerializedResponseWriter.java	(revision 0)
+++ src/java/org/apache/solr/request/PHPSerializedResponseWriter.java	(revision 0)
@@ -0,0 +1,269 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.request;
+
+import java.io.Writer;
+import java.io.IOException;
+import java.util.*;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Fieldable;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.DocIterator;
+import org.apache.solr.search.DocList;
+import org.apache.solr.search.SolrIndexSearcher;
+
+/**
+ * A description of the PHP serialization format can be found here:
+ * http://www.hurring.com/scott/code/perl/serialize/
+ */
+
+public class PHPSerializedResponseWriter implements QueryResponseWriter {
+  static String CONTENT_TYPE_PHP_UTF8="text/x-php-serialized;charset=UTF-8";
+
+  public void init(NamedList n) {
+    /* NOOP */
+  }
+  
+ public void write(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) throws IOException {
+    PHPSerializedWriter w = new PHPSerializedWriter(writer, req, rsp);
+    w.writeResponse();
+  }
+
+  public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
+    return CONTENT_TYPE_TEXT_UTF8;
+  }
+}
+
+class PHPSerializedWriter extends JSONWriter {
+  public PHPSerializedWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) {
+    super(writer, req, rsp);
+    // never indent serialized PHP data
+    doIndent = false;
+  }
+
+  public void writeResponse() throws IOException {
+    writeNamedList(null, rsp.getValues());
+  }
+  
+  @Override
+  public void writeNamedList(String name, NamedList val) throws IOException {
+    writeNamedListAsMapMangled(name,val);
+  }
+  
+  @Override
+  public void writeDoc(String name, Collection<Fieldable> fields, Set<String> returnFields, Map pseudoFields) throws IOException {
+    ArrayList<Fieldable> single = new ArrayList<Fieldable>();
+    HashMap<String, MultiValueField> multi = new HashMap<String, MultiValueField>();
+
+    for (Fieldable ff : fields) {
+      String fname = ff.name();
+      if (returnFields!=null && !returnFields.contains(fname)) {
+        continue;
+      }
+      // if the field is multivalued, it may have other values further on... so
+      // build up a list for each multi-valued field.
+      SchemaField sf = schema.getField(fname);
+      if (sf.multiValued()) {
+        MultiValueField mf = multi.get(fname);
+        if (mf==null) {
+          mf = new MultiValueField(sf, ff);
+          multi.put(fname, mf);
+        } else {
+          mf.fields.add(ff);
+        }
+      } else {
+        single.add(ff);
+      }
+    }
+
+    // obtain number of fields in doc
+    writeArrayOpener(single.size() + multi.size() + ((pseudoFields!=null) ? pseudoFields.size() : 0));
+
+    // output single value fields
+    for(Fieldable ff : single) {
+      SchemaField sf = schema.getField(ff.name());
+      writeKey(ff.name(),true);
+      sf.write(this, ff.name(), ff);
+    }
+    
+    // output multi value fields
+    for(MultiValueField mvf : multi.values()) {
+      writeKey(mvf.sfield.getName(), true);
+      writeArrayOpener(mvf.fields.size());
+      int i = 0;
+      for (Fieldable ff : mvf.fields) {
+        writeKey(i++, false);
+        mvf.sfield.write(this, null, ff);
+      }
+      writeArrayCloser();
+    }
+
+    // output pseudo fields
+    if (pseudoFields !=null && pseudoFields.size()>0) {
+      writeMap(null,pseudoFields,true,false);
+    }
+    writeArrayCloser();
+  }
+  
+  @Override
+  public void writeDocList(String name, DocList ids, Set<String> fields, Map otherFields) throws IOException {
+    boolean includeScore=false;
+    
+    if (fields!=null) {
+      includeScore = fields.contains("score");
+      if (fields.size()==0 || (fields.size()==1 && includeScore) || fields.contains("*")) {
+        fields=null;  // null means return all stored fields
+      }
+    }
+
+    int sz=ids.size();
+
+    writeMapOpener(includeScore ? 4 : 3);
+    writeKey("numFound",false);
+    writeInt(null,ids.matches());
+    writeKey("start",false);
+    writeInt(null,ids.offset());
+
+    if (includeScore) {
+      writeKey("maxScore",false);
+      writeFloat(null,ids.maxScore());
+    }
+    writeKey("docs",false);
+    writeArrayOpener(sz);
+
+    SolrIndexSearcher searcher = req.getSearcher();
+    DocIterator iterator = ids.iterator();
+    for (int i=0; i<sz; i++) {
+      int id = iterator.nextDoc();
+      Document doc = searcher.doc(id, fields);
+      writeKey(i, false);
+      writeDoc(null, doc, fields, (includeScore ? iterator.score() : 0.0f), includeScore);
+    }
+    writeMapCloser();
+
+    if (otherFields !=null) {
+      writeMap(null, otherFields, true, false);
+    }
+
+    writeMapCloser();
+  }
+  
+  @Override
+  public void writeArray(String name, Object[] val) throws IOException {
+    writeMapOpener(val.length);
+    for(int i=0; i < val.length; i++) {
+      writeKey(i, false);
+      writeVal(String.valueOf(i), val[i]);
+    }
+    writeMapCloser();
+  }
+
+  @Override
+  public void writeArray(String name, Iterator val) throws IOException {
+    ArrayList vals = new ArrayList();
+    while( val.hasNext() ) {
+      vals.add(val.next());
+    }
+    writeArray(name, vals.toArray());
+  }
+  
+  @Override
+  public void writeMapOpener(int size) throws IOException, IllegalArgumentException {
+  	// negative size value indicates that something has gone wrong
+  	if (size < 0) {
+  		throw new IllegalArgumentException("Map size must not be negative");
+  	}
+    writer.write("a:"+size+":{");
+  }
+  
+  @Override
+  public void writeMapSeparator() throws IOException {
+    /* NOOP */
+  }
+
+  @Override
+  public void writeMapCloser() throws IOException {
+    writer.write('}');
+  }
+
+  @Override
+  public void writeArrayOpener(int size) throws IOException, IllegalArgumentException {
+  	// negative size value indicates that something has gone wrong
+  	if (size < 0) {
+  		throw new IllegalArgumentException("Array size must not be negative");
+  	}
+    writer.write("a:"+size+":{");
+  }
+
+  @Override  
+  public void writeArraySeparator() throws IOException {
+    /* NOOP */
+  }
+
+  @Override
+  public void writeArrayCloser() throws IOException {
+    writer.write('}');
+  }
+  
+  @Override
+  public void writeNull(String name) throws IOException {
+    writer.write("N;");
+  }
+
+  @Override
+  protected void writeKey(String fname, boolean needsEscaping) throws IOException {
+    writeStr(null, fname, needsEscaping);
+  }
+  void writeKey(int val, boolean needsEscaping) throws IOException {
+    writeInt(null, String.valueOf(val));
+  }
+
+  @Override
+  public void writeBool(String name, boolean val) throws IOException {
+    writer.write(val ? "b:1;" : "b:0;");
+  }
+  
+  @Override
+  public void writeInt(String name, String val) throws IOException {
+    writer.write("i:"+val+";");
+  }
+  
+  @Override
+  public void writeLong(String name, String val) throws IOException {
+    writeInt(name,val);
+  }
+
+  @Override
+  public void writeFloat(String name, String val) throws IOException {
+    writeDouble(name,val);
+  }
+
+  @Override
+  public void writeDouble(String name, String val) throws IOException {
+    writer.write("d:"+val+";");
+  }
+
+  @Override
+  public void writeStr(String name, String val, boolean needsEscaping) throws IOException {
+    // serialized PHP strings don't need to be escaped at all, however the 
+    // string size reported needs be the number of bytes rather than chars.
+    writer.write("s:"+val.getBytes("UTF8").length+":\""+val+"\";");
+  }
+}
Index: src/java/org/apache/solr/request/JSONResponseWriter.java
===================================================================
--- src/java/org/apache/solr/request/JSONResponseWriter.java	(revision 562291)
+++ src/java/org/apache/solr/request/JSONResponseWriter.java	(working copy)
@@ -96,7 +96,7 @@
    */
   protected void writeNamedListAsMapMangled(String name, NamedList val) throws IOException {
     int sz = val.size();
-    writer.write('{');
+    writeMapOpener(sz);
     incLevel();
 
     // In JSON objects (maps) we can't have null keys or duplicates...
@@ -121,7 +121,7 @@
         first=false;
         repeats.put(key,0);
       } else {
-        writer.write(',');
+        writeMapSeparator();
 
         Integer repeatCount = repeats.get(key);
         if (repeatCount==null) {
@@ -145,7 +145,7 @@
     }
 
     decLevel();
-    writer.write('}');
+    writeMapCloser();
   }
 
   /** Represents a NamedList directly as a JSON Object (essentially a Map)
@@ -154,12 +154,12 @@
    */ 
   protected void writeNamedListAsMapWithDups(String name, NamedList val) throws IOException {
     int sz = val.size();
-    writer.write('{');
+    writeMapOpener(sz);
     incLevel();
 
     for (int i=0; i<sz; i++) {
       if (i!=0) {
-        writer.write(',');
+        writeMapSeparator();
       }
 
       String key = val.getName(i);
@@ -170,7 +170,7 @@
     }
 
     decLevel();
-    writer.write('}');
+    writeMapCloser();
   }
 
   // Represents a NamedList directly as an array of JSON objects...
@@ -178,7 +178,7 @@
   protected void writeNamedListAsArrMap(String name, NamedList val) throws IOException {
     int sz = val.size();
     indent();
-    writer.write('[');
+    writeArrayOpener(sz);
     incLevel();
 
     boolean first=true;
@@ -188,7 +188,7 @@
       if (first) {
         first=false;
       } else {
-        writer.write(',');
+        writeArraySeparator();
       }
 
       indent();
@@ -196,16 +196,16 @@
       if (key==null) {
         writeVal(null,val.getVal(i));
       } else {
-        writer.write('{');
+        writeMapOpener(1);
         writeKey(key, true);
         writeVal(key,val.getVal(i));
-        writer.write('}');
+        writeMapCloser();
       }
 
     }
 
     decLevel();
-    writer.write(']');
+    writeArrayCloser();
   }
 
   // Represents a NamedList directly as an array of JSON objects...
@@ -213,7 +213,7 @@
   protected void writeNamedListAsArrArr(String name, NamedList val) throws IOException {
     int sz = val.size();
     indent();
-    writer.write('[');
+    writeArrayOpener(sz);
     incLevel();
 
     boolean first=true;
@@ -223,7 +223,7 @@
       if (first) {
         first=false;
       } else {
-        writer.write(',');
+        writeArraySeparator();
       }
 
       indent();
@@ -234,19 +234,19 @@
       } else {
      ***/
 
-        writer.write('[');
+        writeArrayOpener(1);
         incLevel();
         writeStr(null,key,true);
-        writer.write(',');
+        writeArraySeparator();
         writeVal(key,val.getVal(i));
         decLevel();
-        writer.write(']');
+        writeArrayCloser();
 
 
     }
 
     decLevel();
-    writer.write(']');
+    writeArrayCloser();
   }
 
   // Represents a NamedList directly as an array with keys/values
@@ -254,12 +254,12 @@
   // NamedList("a"=1,"b"=2,null=3) => ["a",1,"b",2,null,3]
   protected void writeNamedListAsFlat(String name, NamedList val) throws IOException {
     int sz = val.size();
-    writer.write('[');
+    writeArrayOpener(sz);
     incLevel();
 
     for (int i=0; i<sz; i++) {
       if (i!=0) {
-        writer.write(',');
+        writeArraySeparator();
       }
       String key = val.getName(i);
       indent();
@@ -268,12 +268,12 @@
       } else {
         writeStr(null, key, true);
       }
-      writer.write(',');
+      writeArraySeparator();
       writeVal(key, val.getVal(i));
     }
 
     decLevel();
-    writer.write(']');
+    writeArrayCloser();
   }
 
 
@@ -292,7 +292,7 @@
   }
 
 
-  private static class MultiValueField {
+  protected static class MultiValueField {
     final SchemaField sfield;
     final ArrayList<Fieldable> fields;
     MultiValueField(SchemaField sfield, Fieldable firstVal) {
@@ -303,7 +303,7 @@
   }
 
   public void writeDoc(String name, Collection<Fieldable> fields, Set<String> returnFields, Map pseudoFields) throws IOException {
-    writer.write('{');
+    writeMapOpener(-1); // no trivial way to determine map size
     incLevel();
 
     HashMap<String, MultiValueField> multi = new HashMap<String, MultiValueField>();
@@ -332,7 +332,7 @@
         if (first) {
           first=false;
         } else {
-          writer.write(',');
+          writeMapSeparator();
         }
         indent();
         writeKey(fname,true);
@@ -344,7 +344,7 @@
       if (first) {
         first=false;
       } else {
-        writer.write(',');
+        writeMapSeparator();
       }
 
       indent();
@@ -357,7 +357,7 @@
         indentArrElems = (mvf.sfield.getType() instanceof TextField);
       }
 
-      writer.write('[');
+      writeArrayOpener(-1); // no trivial way to determine array size
       boolean firstArrElem=true;
       incLevel();
 
@@ -365,12 +365,12 @@
         if (firstArrElem) {
           firstArrElem=false;
         } else {
-          writer.write(',');
+          writeArraySeparator();
         }
         if (indentArrElems) indent();
         mvf.sfield.write(this, null, ff);
       }
-      writer.write(']');
+      writeArrayCloser();
       decLevel();
     }
 
@@ -379,7 +379,7 @@
     }
 
     decLevel();
-    writer.write('}');
+    writeMapCloser();
   }
 
   // reusable map to store the "score" pseudo-field.
@@ -406,23 +406,23 @@
 
     int sz=ids.size();
 
-    writer.write('{');
+    writeMapOpener(includeScore ? 4 : 3);
     incLevel();
     writeKey("numFound",false);
     writeInt(null,ids.matches());
-    writer.write(',');
+    writeMapSeparator();
     writeKey("start",false);
     writeInt(null,ids.offset());
 
     if (includeScore) {
-      writer.write(',');
+      writeMapSeparator();
       writeKey("maxScore",false);
       writeFloat(null,ids.maxScore());
     }
-    writer.write(',');
+    writeMapSeparator();
     // indent();
     writeKey("docs",false);
-    writer.write('[');
+    writeArrayOpener(sz);
 
     incLevel();
     boolean first=true;
@@ -436,13 +436,13 @@
       if (first) {
         first=false;
       } else {
-        writer.write(',');
+        writeArraySeparator();
       }
       indent();
       writeDoc(null, doc, fields, (includeScore ? iterator.score() : 0.0f), includeScore);
     }
     decLevel();
-    writer.write(']');
+    writeArrayCloser();
 
     if (otherFields !=null) {
       writeMap(null, otherFields, true, false);
@@ -450,11 +450,39 @@
 
     decLevel();
     indent();
-    writer.write('}');
+    writeMapCloser();
   }
 
+  //
+  // Data structure tokens
+  // NOTE: a positive size paramater indicates the number of elements
+  //       contained in an array or map, a negative value indicates 
+  //       that the size could not be reliably determined.
+  // 
+  
+  public void writeMapOpener(int size) throws IOException, IllegalArgumentException {
+    writer.write('{');
+  }
+  
+  public void writeMapSeparator() throws IOException {
+    writer.write(',');
+  }
 
+  public void writeMapCloser() throws IOException {
+    writer.write('}');
+  }
+  
+  public void writeArrayOpener(int size) throws IOException, IllegalArgumentException {
+    writer.write('[');
+  }
+  
+  public void writeArraySeparator() throws IOException {
+    writer.write(',');
+  }
 
+  public void writeArrayCloser() throws IOException {
+    writer.write(']');
+  }
 
   public void writeStr(String name, String val, boolean needsEscaping) throws IOException {
     // it might be more efficient to use a stringbuilder or write substrings
@@ -508,7 +536,7 @@
 
   public void writeMap(String name, Map val, boolean excludeOuter, boolean isFirstVal) throws IOException {
     if (!excludeOuter) {
-      writer.write('{');
+      writeMapOpener(val.size());
       incLevel();
       isFirstVal=true;
     }
@@ -523,7 +551,7 @@
       if (isFirstVal) {
         isFirstVal=false;
       } else {
-        writer.write(',');
+        writeMapSeparator();
       }
 
       if (doIndent) indent();
@@ -533,7 +561,7 @@
 
     if (!excludeOuter) {
       decLevel();
-      writer.write('}');
+      writeMapCloser();
     }
   }
 
@@ -542,19 +570,19 @@
   }
 
   public void writeArray(String name, Iterator val) throws IOException {
-    writer.write('[');
+    writeArrayOpener(-1); // no trivial way to determine array size
     incLevel();
     boolean first=true;
     while( val.hasNext() ) {
       if( !first ) indent();
       writeVal(null, val.next());
       if( val.hasNext() ) {
-        writer.write(',');
+        writeArraySeparator();
       }
       first=false;
     }
     decLevel();
-    writer.write(']');
+    writeArrayCloser();
   }
 
   //
@@ -657,143 +685,3 @@
   }
 
 }
-
-class PythonWriter extends JSONWriter {
-  public PythonWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) {
-    super(writer, req, rsp);
-  }
-
-  @Override
-  public void writeNull(String name) throws IOException {
-    writer.write("None");
-  }
-
-  @Override
-  public void writeBool(String name, boolean val) throws IOException {
-    writer.write(val ? "True" : "False");
-  }
-
-  @Override
-  public void writeBool(String name, String val) throws IOException {
-    writeBool(name,val.charAt(0)=='t');
-  }
-
-  /* optionally use a unicode python string if necessary */
-  @Override
-  public void writeStr(String name, String val, boolean needsEscaping) throws IOException {
-    if (!needsEscaping) {
-      writer.write('\'');
-      writer.write(val);
-      writer.write('\'');
-      return;
-    }
-
-    // use python unicode strings...
-    // python doesn't tolerate newlines in strings in it's eval(), so we must escape them.
-
-    StringBuilder sb = new StringBuilder(val.length());
-    boolean needUnicode=false;
-
-    for (int i=0; i<val.length(); i++) {
-      char ch = val.charAt(i);
-      switch(ch) {
-        case '\'':
-        case '\\': sb.append('\\'); sb.append(ch); break;
-        case '\r': sb.append("\\r"); break;
-        case '\n': sb.append("\\n"); break;
-        case '\t': sb.append("\\t"); break;
-        default:
-          // we don't strictly have to escape these chars, but it will probably increase
-          // portability to stick to visible ascii
-          if (ch<' ' || ch>127) {
-            unicodeEscape(sb, ch);
-            needUnicode=true;
-          } else {
-            sb.append(ch);
-          }
-      }
-    }
-
-    writer.write( needUnicode ? "u'" : "'");
-    writer.append(sb);
-    writer.write('\'');
-  }
-
-  /*
-  old version that always used unicode
-  public void writeStr(String name, String val, boolean needsEscaping) throws IOException {
-    // use python unicode strings...
-    // python doesn't tolerate newlines in strings in it's eval(), so we must escape them.
-    writer.write("u'");
-    // it might be more efficient to use a stringbuilder or write substrings
-    // if writing chars to the stream is slow.
-    if (needsEscaping) {
-      for (int i=0; i<val.length(); i++) {
-        char ch = val.charAt(i);
-        switch(ch) {
-          case '\'':
-          case '\\': writer.write('\\'); writer.write(ch); break;
-          case '\r': writer.write("\\r"); break;
-          case '\n': writer.write("\\n"); break;
-          default:
-            // we don't strictly have to escape these chars, but it will probably increase
-            // portability to stick to visible ascii
-            if (ch<' ' || ch>127) {
-              unicodeChar(ch);
-            } else {
-              writer.write(ch);
-            }
-        }
-      }
-    } else {
-      writer.write(val);
-    }
-    writer.write('\'');
-  }
-  */
-
-}
-
-
-class RubyWriter extends JSONWriter {
-  public RubyWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) {
-    super(writer, req, rsp);
-  }
-
-  @Override
-  public void writeNull(String name) throws IOException {
-    writer.write("nil");
-  }
-
-  @Override
-  protected void writeKey(String fname, boolean needsEscaping) throws IOException {
-    writeStr(null, fname, needsEscaping);
-    writer.write("=>");
-  }
-
-  @Override
-  public void writeStr(String name, String val, boolean needsEscaping) throws IOException {
-    // Ruby doesn't do unicode escapes... so let the servlet container write raw UTF-8
-    // bytes into the string.
-    //
-    // Use single quoted strings for safety since no evaluation is done within them.
-    // Also, there are very few escapes recognized in a single quoted string, so
-    // only escape the backslash and single quote.
-    writer.write('\'');
-    // it might be more efficient to use a stringbuilder or write substrings
-    // if writing chars to the stream is slow.
-    if (needsEscaping) {
-      for (int i=0; i<val.length(); i++) {
-        char ch = val.charAt(i);
-        switch(ch) {
-          case '\'':
-          case '\\': writer.write('\\'); writer.write(ch); break;
-          default: writer.write(ch); break;
-        }
-      }
-    } else {
-      writer.write(val);
-    }
-    writer.write('\'');
-  }
-}
Index: src/java/org/apache/solr/request/RubyResponseWriter.java
===================================================================
--- src/java/org/apache/solr/request/RubyResponseWriter.java	(revision 562291)
+++ src/java/org/apache/solr/request/RubyResponseWriter.java	(working copy)
@@ -37,3 +37,46 @@
     return CONTENT_TYPE_TEXT_UTF8;
   }
 }
+
+class RubyWriter extends JSONWriter {
+  public RubyWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) {
+    super(writer, req, rsp);
+  }
+
+  @Override
+  public void writeNull(String name) throws IOException {
+    writer.write("nil");
+  }
+
+  @Override
+  protected void writeKey(String fname, boolean needsEscaping) throws IOException {
+    writeStr(null, fname, needsEscaping);
+    writer.write("=>");
+  }
+
+  @Override
+  public void writeStr(String name, String val, boolean needsEscaping) throws IOException {
+    // Ruby doesn't do unicode escapes... so let the servlet container write raw UTF-8
+    // bytes into the string.
+    //
+    // Use single quoted strings for safety since no evaluation is done within them.
+    // Also, there are very few escapes recognized in a single quoted string, so
+    // only escape the backslash and single quote.
+    writer.write('\'');
+    // it might be more efficient to use a stringbuilder or write substrings
+    // if writing chars to the stream is slow.
+    if (needsEscaping) {
+      for (int i=0; i<val.length(); i++) {
+        char ch = val.charAt(i);
+        switch(ch) {
+          case '\'':
+          case '\\': writer.write('\\'); writer.write(ch); break;
+          default: writer.write(ch); break;
+        }
+      }
+    } else {
+      writer.write(val);
+    }
+    writer.write('\'');
+  }
+}
Index: src/java/org/apache/solr/request/PythonResponseWriter.java
===================================================================
--- src/java/org/apache/solr/request/PythonResponseWriter.java	(revision 562291)
+++ src/java/org/apache/solr/request/PythonResponseWriter.java	(working copy)
@@ -38,3 +38,99 @@
     return CONTENT_TYPE_TEXT_ASCII;
   }
 }
+
+class PythonWriter extends JSONWriter {
+  public PythonWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) {
+    super(writer, req, rsp);
+  }
+
+  @Override
+  public void writeNull(String name) throws IOException {
+    writer.write("None");
+  }
+
+  @Override
+  public void writeBool(String name, boolean val) throws IOException {
+    writer.write(val ? "True" : "False");
+  }
+
+  @Override
+  public void writeBool(String name, String val) throws IOException {
+    writeBool(name,val.charAt(0)=='t');
+  }
+
+  /* optionally use a unicode python string if necessary */
+  @Override
+  public void writeStr(String name, String val, boolean needsEscaping) throws IOException {
+    if (!needsEscaping) {
+      writer.write('\'');
+      writer.write(val);
+      writer.write('\'');
+      return;
+    }
+
+    // use python unicode strings...
+    // python doesn't tolerate newlines in strings in it's eval(), so we must escape them.
+
+    StringBuilder sb = new StringBuilder(val.length());
+    boolean needUnicode=false;
+
+    for (int i=0; i<val.length(); i++) {
+      char ch = val.charAt(i);
+      switch(ch) {
+        case '\'':
+        case '\\': sb.append('\\'); sb.append(ch); break;
+        case '\r': sb.append("\\r"); break;
+        case '\n': sb.append("\\n"); break;
+        case '\t': sb.append("\\t"); break;
+        default:
+          // we don't strictly have to escape these chars, but it will probably increase
+          // portability to stick to visible ascii
+          if (ch<' ' || ch>127) {
+            unicodeEscape(sb, ch);
+            needUnicode=true;
+          } else {
+            sb.append(ch);
+          }
+      }
+    }
+
+    writer.write( needUnicode ? "u'" : "'");
+    writer.append(sb);
+    writer.write('\'');
+  }
+
+  /*
+  old version that always used unicode
+  public void writeStr(String name, String val, boolean needsEscaping) throws IOException {
+    // use python unicode strings...
+    // python doesn't tolerate newlines in strings in it's eval(), so we must escape them.
+    writer.write("u'");
+    // it might be more efficient to use a stringbuilder or write substrings
+    // if writing chars to the stream is slow.
+    if (needsEscaping) {
+      for (int i=0; i<val.length(); i++) {
+        char ch = val.charAt(i);
+        switch(ch) {
+          case '\'':
+          case '\\': writer.write('\\'); writer.write(ch); break;
+          case '\r': writer.write("\\r"); break;
+          case '\n': writer.write("\\n"); break;
+          default:
+            // we don't strictly have to escape these chars, but it will probably increase
+            // portability to stick to visible ascii
+            if (ch<' ' || ch>127) {
+              unicodeChar(ch);
+            } else {
+              writer.write(ch);
+            }
+        }
+      }
+    } else {
+      writer.write(val);
+    }
+    writer.write('\'');
+  }
+  */
+
+}
Index: example/solr/conf/solrconfig.xml
===================================================================
--- example/solr/conf/solrconfig.xml	(revision 562291)
+++ example/solr/conf/solrconfig.xml	(working copy)
@@ -495,6 +495,8 @@
     <queryResponseWriter name="json" class="org.apache.solr.request.JSONResponseWriter"/>
     <queryResponseWriter name="python" class="org.apache.solr.request.PythonResponseWriter"/>
     <queryResponseWriter name="ruby" class="org.apache.solr.request.RubyResponseWriter"/>
+    <queryResponseWriter name="php" class="org.apache.solr.request.PHPResponseWriter"/>
+    <queryResponseWriter name="phps" class="org.apache.solr.request.PHPSerializedResponseWriter"/>
 
     <queryResponseWriter name="custom" class="com.example.MyResponseWriter"/>
   -->
