Archive for the 'PEAR' Category

PEAR site minor redesign

Sunday, January 6th, 2008

pear-icon.png Just noticed there have been some changes on the PEAR site. Check it out - no nav menu on the left, spacier for the main content…

http://pear.php.net/

 

Text_Highlighter 0.7.1 and hiliteme.com updates

Tuesday, January 1st, 2008

In today's busy schedules there's less and less time to give love to our favorite open source projects. Luckily though, there's January 1st when you're supposed to relax (or suffer the consequences of partying on Dec 31st). Anyway, today I found the time to fix two bugs in Text_Highlighter and also include the patch from Daniel Fruzynski that adds support for highlighting VBScript. Wo-hoo!

Also updated hiliteme.com so you can try the package or simply highlight some code before posting it to your blog.

Luckily for me, the PEAR site uses GMT timestamps, so the release date of Text_Highlighter 0.7.1. is Jan 2nd. This way I hope it doesn't look like I need a life that badly.

 

Image_Text 0.6 beta is out

Thursday, April 19th, 2007

» Download here

This is my first PEAR release and I was actually surprised how easy it is to package and roll out a release.

So you have your local copy of the CVS repository that contains the scripts you want to release as part of the package. In order to release, you need package.xml, a configuration file, which you can either create yourself or have a script (which uses PEAR_PackageFileManager) to create the xml file for you.

The pear command line tool does all the rest.

  1. pear convert - creates package2.xml based on your package.xml. (package2 is the newer improved version of package2.xml. You can actually use PEAR_PackageFileManager2 instead and skip this step)
  2. pear package - creates the package archive which then you upload to pear.php.net
  3. pear cvstag package2.xml - tags the cvs repository with a tag figured out from the package2. In my case the tag was RELEASE_0_6_0beta

Thanks!

As stated in the change log notes, many thanks go to Christian Weiske and James Pic for helping out with this release!

 

Performance tunning with PEAR::DB

Tuesday, January 16th, 2007

If you use PEAR::MDB2, you can set a custom debug handler and collect all the queries you execute for debugging and performance tunning purposes, as shown before. But what if you're using PEAR::DB? Well, since PEAR::DB doesn't allow you such a functionality out of the box, you can hack it a bit to get similar results.

Simple app

Let's say you have a simple app:

<?php
require_once 'DB.php';

$dsn = 'mysql://root@localhost/test';
$db =& DB::connect($dsn);
$db->setFetchMode(DB_FETCHMODE_ASSOC);

$sql = 'SELECT * FROM zipcodes';
$result = $db->getAll($sql);
$result = $db->getOne($sql);
$result = $db->getCol($sql);
$result = $db->getAll($sql);
$sql = 'SELECT zipcode FROM zipcodes';
$result = $db->getAll($sql);
$result = $db->getAll($sql);
$sql = 'SELECT CONCAT(zipcode, " - ", city) FROM zipcodes';
$result = $db->getAll($sql);
?>

Of course, this is an oversimplified example, usually you have more included files, class libraries and such, and it's not difficult to lose track of the database work as your app grows in complexity and size.

Now let's debug this app to see what type of database work it does.

Hacking PEAR::DB

In my case, I'm using MySQL, so I need to find the DB/mysql.php file in my PEAR directory. I open that file and find the simpleQuery() method. That's where all queries and up, sooner or later. I find this piece of code:

<?php
if (!$this->options['result_buffering']) {
    $result = @mysql_unbuffered_query($query, $this->connection);
} else {
    $result = @mysql_query($query, $this->connection);
}
?>

Then I hack this piece of code, adidng some lines before and after it. The result:

<?php
// start
$start_time = array_sum(explode(' ',microtime()));
// end

if (!$this->options['result_buffering']) {
    $result = @mysql_unbuffered_query($query, $this->connection);
} else {
    $result = @mysql_query($query, $this->connection);
}

// start
$query_took = array_sum(explode(' ',microtime())) - $start_time;
@$GLOBALS['global_query_counter']++;
@$GLOBALS['all_the_queries'][$GLOBALS['global_query_counter'] . ' - ' . $query] = $query_took;
//end
?>

Now as my app's pages are executed, I'll collect invaluable DB information.

Reporting

Let's see what we've collected.

You can add different types of reports in the footer of your application, or better yet, you can register a shutdown function to do the same. Here are some reporting ideas:

<?php
// report 1.
echo "<pre>All the queries, by the order they are executed:\n";
print_r($GLOBALS['all_the_queries']);
echo '</pre>';

// report 2.
echo "<pre>All the queries, ordered by the time they took, descending:\n";
arsort($GLOBALS['all_the_queries']);
print_r($GLOBALS['all_the_queries']);
echo '</pre>';

// report 3.
$sum = 0;
foreach ($GLOBALS['all_the_queries'] AS $t) {
    $sum += $t;
}
echo '<pre>';
echo 'Total number of queries:   ' . $GLOBALS['global_query_counter'] . "\n";
echo 'Total time spend querying: ' . $sum;
echo '</pre>';

// report 4.
$distinct = array();
foreach ($GLOBALS['all_the_queries'] AS $q=>$t) {
    $parts = explode(' - ', $q);
    unset($parts[0]);
    $query = implode(' - ', $parts);
    @$distinct[$query]++;
}
echo "<pre>How many duplications:\n";
arsort($distinct);
print_r($distinct);
echo '</pre>';
?>

Report results

Let's see what these reports gives us.

All the queries, by the order they are executed:
Array
(
    [1 - SELECT * FROM zipcodes] => 0.00626707077026
    [2 - SELECT * FROM zipcodes] => 0.00730204582214
    [3 - SELECT * FROM zipcodes] => 0.00796985626221
    [4 - SELECT * FROM zipcodes] => 0.00654602050781
    [5 - SELECT zipcode FROM zipcodes] => 0.0058650970459
    [6 - SELECT zipcode FROM zipcodes] => 0.0239379405975
    [7 - SELECT CONCAT(zipcode, " - ", city) FROM zipcodes] => 0.00581502914429
)

All the queries, ordered by the time they took, descending:
Array
(
    [6 - SELECT zipcode FROM zipcodes] => 0.0239379405975
    [3 - SELECT * FROM zipcodes] => 0.00796985626221
    [2 - SELECT * FROM zipcodes] => 0.00730204582214
    [4 - SELECT * FROM zipcodes] => 0.00654602050781
    [1 - SELECT * FROM zipcodes] => 0.00626707077026
    [5 - SELECT zipcode FROM zipcodes] => 0.0058650970459
    [7 - SELECT CONCAT(zipcode, " - ", city) FROM zipcodes] => 0.00581502914429
)

Total number of queries:   7
Total time spend querying: 0.0637030601501

How many duplications:
Array
(
    [SELECT * FROM zipcodes] => 4
    [SELECT zipcode FROM zipcodes] => 2
    [SELECT CONCAT(zipcode, " - ", city) FROM zipcodes] => 1
)

Thanks for reading!

Any comments or suggestions are very welcome!

 

DB-2-MDB2 in Portuguese

Tuesday, January 16th, 2007

Through a trackback I found out that Walter Cruz has translated my DB-2-MDB2 article in a language I was led to believe is Brazilian Portuguese.

Thanks very much Walter, this is very flattering!

Thanks to my buddy Isidoro who enlightened me that the language was Portugeese!

 

Using PEAR and AWS to keep an eye on Amazon

Wednesday, January 10th, 2007

What could possibly be better for a writer's ego other than being read and being praised? Hmm…

So I wanted to have a page that shows the books I've written, together with their Amazon sales rank and the average customer rating and number of reviews. It's really easy. I took one example out of the PEAR book and slightly modified it.

The result is here.

Implementation at a glance

When making a request, you need to say what type of request it is and what type of response you want. It's documented here. For the type of request, look under "API reference -> Operations" and for the response type, look under "API reference -> Response Groups"

The code

<?php

// include the PEAR package
require_once 'Services/AmazonECS4.php';

// Your AWS subscription id
$subscriptionId = '1WQDAES5PQ**********';

// create a new client by supplying
// subscription id
$amazon  = new Services_AmazonECS4($subscriptionId);
$amazon->setLocale('US');

// output options
// what do you need returned?
$options = array();
$options['ResponseGroup'] = 'SalesRank,ItemAttributes,Reviews';

// for which books
// comma-delimited list of ISBNs
$items = '1904811795,1904811914,1904811132';

// do the request
$result = $amazon->ItemLookup($items, $options);

// check for errors
if (PEAR::isError($result)) {
    print "An error occured<br/>";
    print $result->getMessage() . "<br/>";
    exit();
}

// some spaghetti to display HTML response
echo '<ul>';
foreach ($result['Item'] as $book) { // loop the books
    // URL
    echo '<li><a href="'. $book['DetailPageURL'] .'">';
    // book title
    echo $book['ItemAttributes']['Title'], '</a><br />';
    // authors, comma-delimited
    echo implode(', ',$book['ItemAttributes']['Author']);
    // sales rank
    echo '<br />Sales rank: ', $book['SalesRank'];
    // average rating
    if (!empty($book['CustomerReviews'])) {
        echo '<br />Rating: ';
        echo $book['CustomerReviews']['AverageRating'];
        echo ', based on ';
        echo $book['CustomerReviews']['TotalReviews'], ' reviews';

    }
    echo '</li>';
}
echo '</ul>';
?>

<a
  href="#"
  onclick="javascript:document.getElementById('response').style.display='block';"
  >Show complete response</a>
<pre id="response" style="display: none">
<?php print_r($result); ?>
</pre>
 

Reusing an existing database connection with MDB2

Thursday, January 4th, 2007

This is a follow up to a question posted by Sam in my DB-2-MDB2 post. The question was if you can reuse an exisitng database connection you've already established and not have MDB2 creating a second connection.

When using a non-persistent connection

No worries in this case. No new connection will be established. As the PHP manual states:

If a second call is made to mysql_connect() with the same arguments, no new link will be established, but instead, the link identifier of the already opened link will be returned.

That is, if you don't set the fourth parameter to mysql_connect() to true. This parameter forces a new connection. BTW, in MDB2 if you do want to force a new connection, you have to set new_link in the DSN string to true

Bottom line, if you don't do anything special, the existing connection will be reused by MDB2. You can always verify that this is the case by calling phpinfo(INFO_MODULES); and looking in the "mysql" section.

When using a persistent connection

When using a persistent connection you have to do some additional steps to ensure that the same persistent connection is used by MDB2.

  • Tell MDB2 that you want a persistent connection - $mdb2->setOption('persistent', true);
  • Tell MDB2 which connection you want to use - $mdb2->connection = $link;, where $link is your existing connection
  • Set $mdb2->opened_persistent = true;

Here's an example:

<?php
// somewhere you've established a connection
$link = mysql_pconnect('localhost', 'root', '');
mysql_select_db('test', $link);
echo $link; // e.g. Resource id #5

// Create MDB2 object
require_once 'MDB2.php';
$dsn = 'mysql://root@localhost/test';
$mdb2 =& MDB2::factory($dsn);

// reuse your connection
$mdb2->setOption('persistent', true);
$mdb2->opened_persistent = true;
$mdb2->connection = $link;

// connect
$mdb2->connect();
echo $mdb2->connection; // Resource id #5

// check the "mysql" part to be sure
phpinfo(INFO_MODULES);
?>
 

Laziest image resize in PHP

Wednesday, December 13th, 2006

Today I saw a post at digg.com on image resizing with PHP and there was quite a discussion. Let me share the laziest way (that I know of) how to do it - PEAR::Image_Transform is all it takes. Here goes:

<?php
require_once 'Image/Transform.php';
$i =& Image_Transform::factory('');

$i->load('test.jpg');
$i->fit(100,100);
$i->save('resized.png', 'png');
?>

In addition, the Image_Transform library offers diffferent ways (to skin the old cat) to resize an image - by given pixel value, only on the X axis, on Y, scalling in percentage and so on. And, of course, the library can do much more than resizing, as you can see in the API docs.

It supports all kinds of image manipulation extensions - GD, GD1, ImageMagick, NetPBM, Imlib… If you want to use a specific one, you set as a parameter to the factory() method. In the example above I passed an empty string, so it will try to figure out what's available in my PHP setup and use it, trying Imagick2 first, then GD, then Imlib.

You have setOption() and setOptions() methods if you want to play around with the image quality and those sort of things.

 

Performance tuning with MDB2

Saturday, December 9th, 2006

This is a follow-up to Lars' comment about the PEAR book. In the MDB2 chapter I showed an example how you can create custom debug handlers in MDB2 and then gave a suggestion about a useful application of this functionality for performance tuning. Basically the idea is that your custom debug handler collects all queries that are executed during the life of a given script. Then, once the script finishes execution, the debug handler reports the stats that it has collected. In the book, the example is how you count the number of times each distinct query is executed, this way you can spot problems caused by the OO abstraction. For example, say you have a come class Users that has a method loadUser(), which abstracts the database work. While debugging with the custom error handler, you might figure out that without noticing, you're calling this method in a few places and it makes the same repeating query(queries) over and over again. So you can now optimize/cache results and so on.

The suggestion I made in the book is that in addition to counting, you might want to try executing all SELECTs again, just to see how much time they take and you can execute them once again, prepending them with EXPLAIN to get some details on possible room for improvement.

Now here's one solution to this suggestion. What you can see in this script is:

  • Setting up MDB2
  • Declaring a custom debug handler class
  • "Attaching" it to the MDB2 instance
  • Registering it for execution at the end of each script
  • Testing it (creating a DB, table, some queries)

I hope you like it and try it out.

Here's the result of executing this script, you can see what you get back.

Room for improvement

Obviously, the method dumpInfo() can be improved. First, it can print out a nice table, instead of lazy print_r(). Then, it can include some logic, my idea is for it to "understand" the EXPLAIN results and to give you a hint by using colors, for exampe green background for queries that are OK, yellow for warnings and red for queries that definitelly need some work. Could be nice, no?

Test script

Kinda longish, but I hope I added enough comments. I also hope I didn't introduce any syntax errors while formatting it for posting here, chopping long lines, etc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
<?php

// PEAR error handling setup
require_once 'PEAR.php';
function pearError ($e)
{
  echo '<pre>';
  echo $e->getMessage().': '.$e->getUserinfo();
  echo '</pre>';
}
PEAR::setErrorHandling(
  PEAR_ERROR_CALLBACK,
  'pearError'
);

// creating MDB2 instance
require_once 'MDB2.php';
$dsn = 'mysql://root:test@localhost';
$mdb2 =& MDB2::factory($dsn);
$mdb2->setFetchMode(MDB2_FETCHMODE_ASSOC);

// The custom error handler
//
// It will collect all the queries being executed
// in the script, the collection is done by the
// collectInfo() method.
// Once the script finishes executing, we'll call
// the method executeAndExplain() which will
// execute all unique SELECTs once again
// in order to give us an info of how much time
// each query takes.
// Then executeAndExplain() will execute again
// all SELECTs, this time prepending an EXPLAIN
// so that we can get valuable
// optimization-related information
// Not only that but instead of simple EXPLAIN,
// we can use EXPLAIN EXTENDED and after that
// we can call SHOW WARNINGS -
// this will give us even more optimization hints
//
// http://dev.mysql.com/doc/refman/5.1/en/explain.html
// http://dev.mysql.com/doc/refman/5.1/en/show-warnings.html
//
class Explain_Queries
{
  // how many queries were executed
  var $query_count = 0;
  // which queries and their count
  var $queries = array();
  // results of EXPLAIN-ed SELECTs
  var $explains = array();
  // the MDB2 instance
  var $db = false;

  // constructor that accepts MDB2 reference
  function Explain_Queries(&$db) {
    $this->db = $db;
  }

  // this method is called on every query
  function collectInfo(
    &$db,
    $scope,
    $message,
    $is_manip = null)
  {
    // increment the total number of queries
    $this->query_count++;
    // the SQL is a key in the queries array
    // the value will be the count of how
    // many times each query was executed
    @$this->queries[$message]++;
  }

  // print the debug information
  function dumpInfo()
  {
    echo '<h3>Queries on this page</h3>';
    echo '<pre>';
    print_r($this->queries);
    echo '</pre>';
    echo '<h3>EXPLAIN-ed SELECTs</h3>';
    echo '<pre>';
    print_r($this->explains);
    echo '</pre>';
  }

  // the method that will execute all SELECTs
  // with and without an EXPLAIN and will
  // create $this->explains array of debug
  // information
  // SHOW WARNINGS will be called after each
  // EXPLAIN for more information
  function executeAndExplain() {

    // at this point, stop debugging
    $this->db->setOption('debug', 0);
    $this->db->loadModule('Extended');

    // take the SQL for all the unique queries
    $queries = array_keys($this->queries);
    foreach ($queries AS $sql) {

      // for all SELECTs…
      $sql = trim($sql);
      if (stristr($sql,"SELECT") !== false){
        // note the start time
        $start_time = array_sum(
            explode(" ", microtime())
        );
        // execute query
        $this->db->query($sql);
        // note the end time
        $end_time = array_sum(
            explode(" ", microtime())
        );
        // the time the query took
        $total_time = $end_time - $start_time;

        // now execute the same query with
        // EXPLAIN EXTENDED prepended
        $explain = $this->db->getAll(
          'EXPLAIN EXTENDED ' . $sql
        );

        $this->explains[$sql] = array();
        // update the debug array with the
        // new data from
        // EXPLAIN and SHOW WARNINGS
        if (!PEAR::isError($explain)) {
          $this->explains[$sql]['explain'] = $explain;
          $this->explains[$sql]['warnings'] =
               $this->db->getAll('SHOW WARNINGS');
        }

        // update the debug array with the
        // count and time
        $this->explains[$sql]['time'] = $total_time;
      }
    }
  }
}

// instance of the custom debug handler
$my_debug_handler = new Explain_Queries($mdb2);
// set debug option
$mdb2->setOption('debug', 1);
// set debug handler to the method that
// collects all queries
$mdb2->setOption(
  'debug_handler',
  array($my_debug_handler, 'collectInfo')
);
// register functions to be executed on shut down
// after the script has finished execution.
// Now that the show's over, it's the time to
// report what happened in this script db-access-wise
// First shutdown function executes the
// SELECTs again, the other one prints the results
register_shutdown_function(
  array($my_debug_handler, 'executeAndExplain')
);
register_shutdown_function(
  array($my_debug_handler, 'dumpInfo')
);

//
//
// At this point all MDB2 setup is done,
// time for the actual script to do something
//
//

// load the DB manager module
$mdb2->loadModule('Manager');

// drop database if it exists
// temporarily change the PEAR error handling
PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
$mdb2->dropDatabase('test_db_explain');
PEAR::popErrorHandling();

// create and set a new database
$mdb2->createDatabase('test_db_explain');
$mdb2->setDatabase('test_db_explain');

// create table "events" from a definition array
// the table has event ID, name and date/time
$definition = array (
  'id' => array (
    'type' => 'integer',
    'unsigned' => 1,
    'notnull' => 1,
    'default' => 0,
  ),
  'name' => array (
    'type' => 'text',
    'length' => 255
  ),
  'datetime' => array (
    'type' => 'timestamp'
  )
);

$mdb2->createTable('events', $definition);

// create a primary key - the ID field
$definition = array (
  'primary' => true,
  'fields' => array (
    'id' => array()
  )
);
$mdb2->createConstraint(
  'events',
  'myprimekey',
  $definition
);

// load the class that has some static helper
// functions to work with MDB2's cross-RDBMS
// date format
MDB2::loadFile('Date');

// INSERT
// some data to insert into the events table
$data = array(
  // using MDB2-managed sequences
  'id'     => $mdb2->nextId('events'),
  'name'     => "Breakfast a Tiffany's",
  'datetime'   => MDB2_Date::unix2Mdbstamp(
    strtotime('Jan 15, 2007')
  )
);
// The "datetime" value shows how you can use
// any date format you wish as long as you're
// able to get a unix timestamp out of it
// In this case I'm using strtotime()
// Then there is a call to MDB2's date helper
// to get the MDB2 timestamp

// auto insert
// for the autoExecute() method we need to
// load the Extended module
$mdb2->loadModule('Extended');
$result = $mdb2->autoExecute(
  'events',
  $data,
  MDB2_AUTOQUERY_INSERT
);

//
// Time to SELECT something
//
// Using date helpers again
$start_date = MDB2_Date::date2Mdbstamp(0,0,0,12,31,1980);
$end_date   = MDB2_Date::date2Mdbstamp(0,0,0,12,31,2020);
$sql = 'SELECT * FROM %s WHERE %s > %s AND %s < %s';
$sql = sprintf(
  $sql,
  $mdb2->quoteIdentifier('events'),   // quote table name
  $mdb2->quoteIdentifier('datetime'), // quote field name
  $mdb2->quote($start_date, 'date'),  // quote data as date
  $mdb2->quoteIdentifier('datetime'), // quote field name
  $mdb2->quote($end_date,   'date')   // quote data as date
);
$res = $mdb2->getAll($sql); // execute

//
// * Bad practice code follows *
// Just some more inserts and selects, bad
// practice because these queries are not
// necessarily portable accross various RDBMS,
// lacking proper quoting and preparation
for ($i = 2; $i < 31; $i++) {
  $mdb2->query(
    'INSERT INTO events VALUES('
    . $i
    . ', "test event", "2005-05-05 00:00:00")'
  );
}
$res = $mdb2->getCol(
  'SELECT DISTINCT datetime FROM events'
);
$res = $mdb2->getRow(
  'SELECT * FROM events WHERE id
  IN (SELECT id FROM events WHERE id > 1)'
);
?>
 

Personal news update Nov/06

Wednesday, November 1st, 2006

So what I've been up to recently? Having a bit of a break, I guess. First, I'm not currently writing a book, after the last one for which I completed my chapter back in June. That's some free time (There is a very exciting book project on the horizon though, we'll see). Then, I'm not working so much on our new house. Now (August) that my family is back from Bulgaria, and the two little princesses are running around, it's next to impossible to do any construction work, however small.

I changed my job not so long ago, I'm working for SAP now, here in good old Old Montreal. Meeting smart people every day and learning new things, SAP has quite a bit going on and there's always something to learn. Take the proprietary ABAP programing language for one.

As always a PHP junkie, I looked into what's possible in terms of integration of PHP and SAP. Turns out it's possible and it's fun. There is this open-source SAPRFC PHP extension, that allows you to use PHP to connect to an SAP system and do stuff. Out of this interest a few things happened:

  • I published an article at the International PHP Magazine about a tool (or more like a collection of tools), called Scripting In A Box which is developed at SAP. It's one big archive (as in ZIP) which you unpackage to your C:\ drive and you get Apache, MySQL, PHP(+SAPRFC), Perl, Ruby/ROR, Python, Eclispse(+PHPEclipse), all pre-configured and running together. So you can start scripting in minutes. This tool actually gave me a chance to try out and love PHPEclipse, something I've missed, being so attached to my TextPad and ignoring any other way to do PHP. Now I can highly recommend PHPEclipse as a PHP IDE.
  • Next, some folks at SAP (Thanks Craig, AndrĂ©!) recognized my PHP experience and asked me to do a little demo of SAP+PHP at SAP's big event, called TechEd in Las Vegas. This was quite an experience! Las Vegas is one different place and the conference itself was pretty big with 5000 people, I think. I had a chance to meet guys from SDN (SAP Developers Network), which is quite a vivid community with something like half a million members. You know what's the everage response time when you post a question on the SDN forums? 7 minutes.
  • Then, I wrote another article for IPM, which described the demo I did at TechEd. (I'll add the source code and some screenshots at the bottom of this post.)
  • I also contributed one new container for the PEAR::Auth package, it allows you to authenticate users against an SAP system in your PHP app.
  • Another contribution to PEAR was the ABAP language definition for the Text_Highlighter package
  • Finally, I did a little ABAP console, but I'll blog about it seperately and will share the code, of course.
  • … and my first posting on SDN was published today. The next one will be a sort of a cross-post here and on SDN about the ABAP console thingie.

I think that's about it for SAP. Otherwise, as usual, I get easily excited by different things, so I've been doing this and that, here and there, on my own terms, relaxing, without any deadlines preasure.

On the pipeline, I have a bit of stuff to do, again, small little things I enjoy doing, like helping with one article for the PEAR::MDB2 manual, assembling an extra intro chapter for the PEAR book, helping out with Text_Highlighter (I'm this package's official helper since a few days ago), also doing some work for the Image_Text PEAR package, as well as anything else that comes into the radar any given day. Yeah, this is how I understand relaxation, doing whatever you're passionate about, even if this means less sleep at night. Ah, and I have the test for Canadian citizenship tomorrow, so I should be reading as opposed to writing now (I always do this, the busier I am, the more interesting things I "shoehorn")

SAPRFC/PHP demo files

Yeah, the demo app uses YUI and a bit of AJAX and animation to make it a bit sexier.

 

JSON renderer for Text_Highlight

Friday, October 27th, 2006

Text_Highlighter is one of my favourite PEAR packages, seems like I'm addicted to highlighting source code. After adding BB code and simple HTML renderers and an ABAP code syntax definition, today I played with adding a JSON renderer. Useful in case you want to get highlighted source code in your new shiny AJAX app.

Array renderer

After I did the JSON renderer, I said, OK, what if I want to tweak the JSON output just a bit (or the output from any renderer for that matter)? Add more options? Nah, I had a better idea, I scrapped the whole thing and did an Array renderer first. If you have the array output from the renderer, it's trivial to format it as JSON, or XML, or HTML, or anything. I believe even the exisitng Text_Highlighter renderers should be rewritten, to extend an Array renderer. Anyway, back to JSON.

Demo

To see the JSON renderer in action, you can go to my hiliteme.com site and check the JSON tab.

Source

The source code is available here - JSON.phps which extends Array.phps. To test, you need to add the two renderers to your PEAR repository under Text/Highlighter/Renderer

Example

So let's say you need to highlight the PHP code

<?php
    echo "Hello Highlighted World!";
?>

You create an instance of Text_Highlighter and Text_Highlighter_Renderer_JSON and call the highlight() method, assuming that the code you need highlighted is in $source

<?php
// dependencies
require_once 'Text/Highlighter.php';
require_once 'Text/Highlighter/Renderer/JSON.php';

// instance
$json_options = array();
$json_options['tabsize'] = 4;
$json_options['enumerated'] = true;
$renderer =& new Text_Highlighter_Renderer_JSON($json_options);
$highlighter =& Text_Highlighter::factory($_POST['language']);
$highlighter->setRenderer($renderer);

// do the highlighting
$json_result = $highlighter->highlight($source);
?>

Now $json_result will look like:

[["inlinetags","&lt;?php"],["code"," \n    "],["reserved","echo"],["code"," "],["quotes","&quot;"],["string","Hello Highlighted World!"],["quotes","&quot;"],["code","; \n"],["inlinetags","?&gt;"]]

As you see the JSON output is an array, one element per highlighted keyword, and in this array there is a sub array - class/keyword. If you want to display this in your page (let's say you got it from an AJAX call), you can do a loop through the array and surround the keywords with span tags of the selected style:

// say your ajax call returned var src 
// var src = xmlhttp.responseText;
var data = eval(src); 

var res = '';
for (var i in data) {
    res += '<span class="hl-' + data[i][0] + '">';
    res += data[i][1];
    res += '</span>';
}

var el = document.getElementById('some-div').
el.innerHTML = '<pre>' + res + '</pre>';

Here I used innerHTML, you can also use DOM, but in this case you need a special case for the "\n" so that you can create a br element to address IE's habit of ignoring line feeds in a DOM-generated pre tag.

BTW, if you don't set the enumerated option to true, you'll get objects inside the main array, check hiliteme.com's JSON tab for an idea of how this would look like.

 

The PEAR book

Sunday, October 15th, 2006

PEAR bookIn case you've missed it - the PEAR book hit the streets! The exact title is "PHP Programming with PEAR" and it's co-written by Stephan Schmidt, Carsten Lucke, Aaron Wormus and yours truly. Aaron also put up a Wiki for book and PEAR-related updates, it's at thepearbook.com

I tried to put up a list of the packages and classes covered in the book, I've probably missed some classes, especially Date_* and Calendar_* ones, but I hope I got all the packages. Here goes (alphabetically) :

  • Calendar
  • Date
  • Date_Holidays
  • Date_Span
  • Date_Timezone
  • File_PDF
  • HTML_Table
  • HTML_Table_Matrix
  • HTTP_Request
  • MDB2
  • MDB2_Schema
  • Services_AmazonESC4
  • Services_Ebay
  • Services_Google
  • Services_Technorati
  • Services_Webservice
  • Services_Yahoo_Search
  • Spreadsheet_Excel_Writer
  • Structures_DataGrid
  • Structures_DataGrid_Column
  • Structures_DataGrid_DataSource
  • XML_Beautifier (mention)
  • XML_FastCreate
  • XML_Parser
  • XML_RPC
  • XML_RPC_Client
  • XML_RPC_Message
  • XML_RPC_Response
  • XML_RPC_Server
  • XML_RPC_Value
  • XML_RSS
  • XML_Serializer
  • XML_Util
  • XML_XUL

For more info on a package, you can consult the PEAR site and manual. Did you know that you can access a package's page by typing its name (case insensitive) in the URL after pear.php.net, like http://pear.php.net/mdb2_SCHEMA for example?

 

ABAP code syntax highlighting

Tuesday, September 19th, 2006

Just finished and eager to share - I added a new syntax definition to the Text_Highlighter PEAR package (see also here). It's for highlighting code written in the SAP's own ABAP programming language.

A live demo is available at the hiliteme.com site, just pick ABAP from the drop-down of programming languages. Any feedback is appreciated, because it's a brand new thing and may have bugs or incompletenesses (is that a word?). So feel free to highlight your ABAP code and post it to blogs or forums.

The implementation wasn't hard, the Text_Highlighter package is made to be extended and even provides the tools for that. All you need to do is create an XML file that contains keywords and other syntax rules, such as formats of the comments and so on. Then there is a command line tool that takes the XML file and generates a class out of it. The class is later on used when highlighting. Here's the XML file in case you want to improve on it and generate your own ABAP.php class:

 

The PEAR book is on it's way

Tuesday, September 12th, 2006

PEAR book Here's the link to publisher's page dedicated to the PHP Programming with PEAR. Guess who wrote the chapter for MDB2?