How to browse PHP’s print_r output the right way

16 06 2010

I’ve had the pleasure recently of working with some complex object oriented PHP with massive objects or lists of objects. The easiest way to have a look at your data for analyzing and debugging is to print_r it. Unfortunately, skimming through this data can be tedious, especially if you want to skip a couple of large, nested objects that are irrelevant to your data analysis.

When programming, I always keep Notepad++ open so I can keep a bunch of data accesible in tabs, such as notes, texts, data files, etc (and my tabs of saved files are seamlessly preserved between sessions, which is critical, of course). Notepad++ can handle bunches of different file formats out of the box, but PHP print_r isn’t one of them.

So I’ve pasted my print_r output into Notepad++, and the new file language is “Normal Text”. Immediately, bracket matching/highlighting works, which is great. I can go to an opening parens and see where it ends. So that’s it, right? Well, Notepad++ can also do code folding, like when you’re browsing a class, and you want to see all the class functions without all that pesky function code cluttering your view. So if you’ve got a PHP file open, for example, you can either click the [-]  to the left of the code to fold that function, or go to View->Fold All, and then just click [+] to open the class and see the top-level items in the class in plain view.

So what about the print_r? Matlab! I haven’t used Matlab since my first or second year in engineering school, and never thought it would useful, but here it is. I don’t remember what the code looks like, and I don’t care. All I know is that when I select Language->Matlab, it let’s me do code folding on my print_r output, and that’s all that matters. A bunch of the other languages work well too, or the same as Matlab, but it was the first and best for my needs.

FYI, this breaks if you have variable data containing a chunk of text with newlines and whatnot, but so it goes.





(PHP) Timestamp for a WORKING date/time a number of minutes/days from a given start date

3 12 2009

That title long enough? It’s hard to describe this function, but you get it now, right?

Based on this function here.

/**
* getTimePlusOffsetDuringWork
*
* The function returns the timestamp of a date + business minutes
* @param timestamp|string startDate Beginning date/time as base for offset
* @param numeric offsetMinutes Work minutes to add to the startDate
* @param mixed Array of holiday dates, preferrably in the 'MM-DD' format
* @return integer Timestamp for the resulting time during Work hours,
*     given the simple equation StartDate + offsetMinutes.
*     Ignores after-hours, weekends, and holidays [for the first year after the startDate].
* Limitations: too many to list entirely.
*     9am-5pm work day only
*     1 year of holidays only
*     no week/weekday dependent holidays
*     Need to set timezone prior to calling this function if used outside of the server's timezone
*     Relies on English locale setting
* @author Zvi Landsman
**/
function getTimePlusOffsetDuringWork($startDate,$offsetMinutes,$holidays=array("12-25","12-26","01-01","07-04")){
    $j = $i = 0; //changed this to 0 or you were always starting one day ahead
    $tmp1 = validation::is_timestamp($startDate)?$startDate:strtotime($startDate); //worked out a timstamp to start with
    $offsetMinutes = round($offsetMinutes);
    $simple_time = strtotime("+$offsetMinutes minutes", $tmp1);
    if($simple_time <= strtotime('17:00', $tmp1))
    {
        //offset is still today
        return $simple_time;
    }
    //Check if start time is after-hours 
    if ($tmp1 > strtotime('17:00', $tmp1))
    {
        $tmp1 = strtotime("09:00 +1 day", $tmp1);
    }
    //This checks if the minute offset puts us into tomorrow
    $newdate = $tmp1;
    $minutes_left = $offsetMinutes%480;
    $newtime = strtotime("+ $minutes_left minutes", $newdate);
    if($newtime > strtotime('17:00', $newdate))
    {
        $j++;
        $minutes_left = round(($newtime - strtotime('17:00', $newdate))/60);
        $tmp1 = strtotime("+ $minutes_left minutes", strtotime("09:00 +1 day", $tmp1));
    }
    else
    {
        $tmp1 = strtotime("+ $minutes_left minutes", $tmp1);
    }

    //create the holiday list for the first upcoming occurances only
    $holidayList = array();
    foreach ($holidays as $holiday)
    {
        $time_stamp=strtotime($holiday, $tmp1);

        $date=($time_stamp < $tmp1)?
            strftime("%d-%m-%Y",strtotime($holiday.' +1 year')):
            strftime("%d-%m-%Y",$time_stamp);
        $holidayList[] = $date;
    }

    $days_to_offset = floor($offsetMinutes/480);
    while($i < $days_to_offset)
    {
        $tmp2 = strtotime("+$j day", $tmp1);
        $day = strftime("%A",$tmp2);

        $tmp = strftime("%d-%m-%Y",$tmp2);
        if(($day != "Sunday") && ($day != "Saturday" )&&(!in_array($tmp, $holidayList)))
        {
            $i = $i + 1;
            $j = $j + 1;

        }
        else
        {
            $j = $j + 1;

        }
    }
    //$j = $j -1;
    return strtotime("+$j days",$tmp1);
}

I’m completely open to improvements in the comments.





How to play an .asf stream on the iPhone/iPod Touch

24 10 2009

I loved adding my Shoutcast, AOL Radio and Last.fm apps to my iPod, but I was bummed when I couldn’t play my favorite O&A stream on my Touch, because it was in .asf format. I’ve never been one to pay attention to file format. I mean, if my winamp lite couldn’t play any audio, Iwas sure there was a plug-in for it. But this darn iPhone…

Anyway, I looked for a bunch of solutions, but none of them worked. I gave up and tried again a few weeks later, and noticed something out the corner of my eye.

My video re-streamer, the only damn solution I could find to being able to browse my network and play networked files, supported re-streaming Internet Radio too, in any standard format my computer can play/decode. Thank you again Orb/Orblive !

It took me a while to figure out how to add it – I did it through the browser, but then I realized I can do it from my iPhone app. Anyway, thanks for almost getting it (almost) right iPhone.

[Yeah, I upgraded from my $12 mp3 player to a Touch – it just made sense for replacing my Palm Pilot too]





Scriptaculous Image Hover Effect

16 06 2009

I’ll start with my editorializing; perhaps we (earlier Symfony/RoR users) chose poorly when integrating Prototype and Scriptaculous into the framework. It seems that jQuery is taking over, and a look at some of the Plugins being made for Symfony 1.2 are recognizing this, and moving some of the “Protaculous” functions out of the core and developing for jQuery. A search for specific Scriptaculous snippets provide fewer or no results relative to the same search for a jQuery snippet. Live and Learn.

Anyway, I was hoping to add some animation/effects to my otherwise very static website, and thought a simple hover-popup on the profile images could provide a small yet effective effect to give the site some modern motion. Here’s how I did it with the latest attempt for Scriptaculous Effects.

Based on these functions mentioned in the question here, I created these still simple functions:

var z1=0;
function popIn(ev) {
  item = Event.element(ev);
  if(!item.id)item.id='pop_'+(++z1);
  new Effect.Scale(item, 110, {scaleFromCenter:true,
    duration: .4, queue:{ position: 'end', scope: item.id }});
}

function popOut(ev) {
  item = Event.element(ev);
  new Effect.Scale(item, 91, {scaleFromCenter:true,
    duration: .4, queue:{ position: 'end', scope: item.id }});
}

For some brief explanation, these functions to be called on moveover/out events identify the item, if the item has no id, it creates one for it. The Scale effect “smoothly” zooms the element, and uses a scoped queue so that the zoom out will only be triggered after the zoom in completes, but multiple images can be zooming at once.

The effects can be attached with a call such as:

z=$$('.profile img');
z.each(function(n) {
  n.absolutize();
  n.observe('mouseover', popIn);
  n.observe('mouseout', popOut);
});

[Editor’s note: it was brought to my attention that the above call might fail if the images (when attaching to images) were not yet loaded. As such, we should wrap the above call in a

Event.observe(window, 'load', function() {...});

if the images are loaded with the document, or if they’re loaded later (e.g. AJAX), deal with both cases, when the images are and are not yet loaded:

z=$$('.profile img');
z.each(function(n) {
  if(n.complete){
    n.absolutize();
    n.observe('mouseover', popIn);
    n.observe('mouseout', popOut);
  }else{
    n.observe('load', function(ev) {
      n=Event.element(ev);n.absolutize();
      n.observe('mouseover', popIn);
      n.observe('mouseout', popOut);
    });
  }
});

]

Use the selector to find the elements to zoom, and attach the event observers to each.
Place this code after the elements are loaded, and BAM, you’re good to go. It’s nothing too fancy, but it works.

There’s a similar effect with jQuery found here which uses more CSS and seems prettier. I’m open to improvements to this snippet.

As an aside, the only decent site I’ve found with a few custom-made snippets is the Script.aculo.us Samplr (see the sidebar, not the main area). If only there were more of these, Scriptaculous might have a chance for survival.





How to Embed AJAX(!) Forms in Symfony 1.2 Admin Generator

4 05 2009

Based on a few informative articles, helping a veteran Symfony 1.0 programmer navigate through the 1.2 form structure, I was able to embed forms in an admin generated form based on a one-to-many relationship. See those articles at:

How to Embed Forms in Symfony 1.2 Admin Generator
Interactive embedded forms

For the sake of speed, though, I wanted to be able to add more embedded forms dynamically via AJAX before submitting the whole form. Here’s how I did it:

Adding the initial forms; on edit, load the related objects with a delete button; on new object or edit, add 3 blank embedded forms to start with.

// lib/forms/PoForm.class.php
public function configure() {
  sfLoader::loadHelpers(array('jQuery','Asset','Tag','Url'));
   $index = 0;
   foreach ($this->getObject()->getItemPos() as $book){
    $this->embedForm('item_pos'.++$index, new ItemPoForm($book));
    $label = "Item $index".jq_link_to_remote(image_tag('/sf/sf_admin/images/delete'), array(
      'url'     =>  'po/deleteItem?idd='.$book->getId(),
      'success' =>  "jQuery('.sf_admin_form_field_item_pos$index').remove();",
      'confirm' => 'Sure???',
    ));
    $this->widgetSchema->setLabel('item_pos'.$index,$label);
  }
  $a=sfContext::getInstance()->getUser()->getAttribute('N1added'.$this->getObject()->getId());
  $more = $this->getObject()->isNew()?max(3,$a-$index):($a>($index+3)?$a-$index:3);
  for($i=0;$i<$more;$i++){
    $ip = new ItemPo();
    $ip->setPo($this->getObject());
    $this->embedForm('item_pos'.++$index, new ItemPoForm($ip));
    $this->widgetSchema->setLabel('item_pos'.$index,"Item $index");
  }

  $label = "Item $index".jq_link_to_remote(image_tag('/sf/sf_admin/images/add'), array(
    'url'     =>  'po/addItems?po='.$this->getObject()->getId().'&index='.$index,
    'update'  =>  'sf_fieldset_none',
    'position'=>  'bottom',
  ));
  $this->widgetSchema->setLabel('item_pos'.$index,$label);

  sfContext::getInstance()->getUser()->setAttribute('N1added'.$this->getObject()->getId(), $index);
}

The last item on our list gets an “add” image, which makes an AJAX call to add new blank forms.
You can see we also use a user attribute to store how many embedded forms are being displayed, so we know what index we’re up to when we add more forms, and we know how many forms to regenerate on error.

Let’s create the add and delete actions-

// apps/frontend/modules/po/actions/actions.class.php
public function executeDeleteItem(sfWebRequest $request) {
   $sub_category = ItemPoPeer::retrieveByPK($request->getParameter('idd'));
  if (!$sub_category) {return $this->renderText('Error '.$request->getParameter('idd'));}
  $sub_category->delete();
  if (!$this->getRequest()->isXmlHttpRequest()){
    $this->redirect('@po_edit?id='.$sub_category->getPo()->getId());
  }
  return $this->renderText('');
 }
 public function executeAddItems(sfWebRequest $request){
  $po = $request->getParameter('po');
  $index = sfContext::getInstance()->getUser()->getAttribute('N1added'.$po);
  sfLoader::loadHelpers(array('jQuery','Asset','Tag','Url'));
  $form = new PoForm();
  $form->setWidgets(array('id' => new sfWidgetFormInputHidden(),));
  $form->setValidators(array('id' => new sfValidatorPropelChoice(array('model' => 'Po', 'column' => 'id', 'required' => false)),));
  $widgetSchema = $form->getWidgetSchema();
  $widgetSchema->setNameFormat('po[%s]');

  for($i=0;$i<3;$i++){
    $ip = new ItemPo();
    if($po) $ip->setPoId($po);
    $form->embedForm('item_pos'.++$index, new ItemPoForm($ip));
    $widgetSchema->setLabel('item_pos'.$index,"Item $index");
  }

  $label = "Item $index".jq_link_to_remote(image_tag('/sf/sf_admin/images/add'), array(
    'url'     =>  'po/addItems?po='.$po.'&index='.$index,
    'update'  =>  'sf_fieldset_none',
    'position'=>  'bottom',
  ));
  $widgetSchema->setLabel('item_pos'.$index,$label);

  sfContext::getInstance()->getUser()->setAttribute('N1added'.$po, $index);
  $this->form = $form;
  return $this->renderPartial('po/items');
 }

The po/items partial is just a snippet from the auto-generated _form.php-

<?php foreach($configuration->getFormFields($form, 'new') as $fields): ?>
 <?php foreach ($fields as $name => $field): ?>
 <?php if ((isset($form[$name]) && $form[$name]->isHidden()) || (!isset($form[$name]) && $field->isReal())) continue ?>
 <?php include_partial('po/form_field', array(
  'name'       => $name,
  'attributes' => $field->getConfig('attributes', array()),
  'label'      => $field->getConfig('label'),
  'help'       => $field->getConfig('help'),
  'form'       => $form,
  'field'      => $field,
  'class'      => 'sf_admin_form_row sf_admin_'.strtolower($field->getType()).' sf_admin_form_field_'.$name,
 )) ?>
 <?php endforeach; ?>
<?php endforeach; ?>

Finally, we modify our form’s bind method to remove unused embedded forms, and link the rest to our main object-

// lib/forms/PoForm.class.php
  public function bind(array $taintedValues = null, array $taintedFiles = null)
  {
    for($i=1;$i<=sfContext::getInstance()->getUser()->getAttribute('N1added'.$this->getObject()->getId(),3);$i++)
    {
      if(!isset($taintedValues["item_pos$i"]) || empty($taintedValues["item_pos$i"]['item_id']))
      {
        $this->embeddedForms['item_pos'.$i]->getObject()->setDeleted(true);
        unset($this->embeddedForms["item_pos$i"],$this->validatorSchema["item_pos$i"]);
        unset($taintedValues['item_pos'.$i]);
      } else {
        $this->embeddedForms['item_pos'.$i]->getObject()->setPo($this->getObject());
      }

    }
    return parent::bind($taintedValues,$taintedFiles);
  }

That should do it!
(In case you’re having trouble seeing the code, you can go here to see it all)

symfony embedded forms ajax

symfony embedded forms ajax

edit form example

edit form example





What do you get from budget ($12) mp3 players?

11 04 2009

Pretty much what you pay for…

For several years now, I’ve been using my Tungsten Palm for listening to MP3s. I liked being able to test out a few software players, and find one that matched the features I was looking for (I used a combination of pTunes and TCPMP – I especially liked how each would hold my place, so I could pick up where I left off on more than one audio playlist).

Problem was, I went through several Palm devices, mostly because the audio jack was not cut out for extended use, and I’m no good at soldering. The most recent time I lost my MP3 capabilities, I turned for the cheapest solution I could find. I looked online, and found the least expensive 2GB player I could find in Israel (it’s actually gone up in price since I bought it). At about $12, at least I got an inexpensive 2GB stick, and if the MP3 part works too, well I’m good to go.

Here’s my rundown:

  1. Size
    At a little bit larger than my top thumb knuckle, this little sucker is a breeze to carry around without thinking (the ear-buds are the inconvenient part of this mini wonder). Since I’m used to my bulky Palm which I now leave at home most of the time, I’d say I’m ahead here. And with a street name (there’s no branding on the device whatsoever, but I did some research and found some similar looking things online) the Clip, it often stays snugly hanging off my front pocket.
  2. Storage
    It’s got the same space as the SD Card in my Palm. But not all memory is created equal. The player connects to my computer via USB, no problem, but the write speed is atrocious. I think it slows down my computer in general when it’s connected, but certainly any disc access suffers. Deleting and copying data takes at least 10X the equivalent SD transfers using my built-in card reader.
  3. Power
    I use both outlet and USB charging for both devices, but the Clip won’t let me listen while it’s plugged in. Charging goes quite quickly (15-30 minutes for a full charge), but only lasts about 2.5 hours. That means when I’m listening to my 4 hour radio shows, I get more than halfway, then I have to stop, charge, and go again.
  4. Interface
    I already told you about the software on the Palm – the Clip has no screen, and what looks like a wheel, but is actually just 5 buttons for play/pause, volume up, down, forward, and back. And since advancing forward is so common, such as when I want to skip to a track I like (click, click, click) or fast-forward (click and hold tight), that forward button is almost dead. The most frustrating part is when dealing with that forward button – if I accidentally double-click when in the middle of fast forwarding, I skip to the next track, and need to click once to go back, and hold it down all over again. And the fast forwarding is maybe 2X, so you can forget about scrolling to the end of a 4 hour show. I have to pre-slice these recordings every 10 minutes, just in case I lose my place.

After a few weeks, I feel that I already got what I paid for. Maybe I’ll splurge soon on the $25 devices with a screen and see how it goes.





jQuery LTR/RTL News Ticker

25 03 2009

I’ve modified the wonderfully simple jQuery News Ticker, originally published here: http://www.gcmingati.net/wordpress/wp-content/lab/jquery/newsticker/jq-liscroll/scrollanimate.html

The ticker now allows for another parameter, rtl: true, to allow for Right-to-Left scrolling for applicable fonts (such as Hebrew).

Download

Some of the original css syntax has been modified for stylistic reasons.

Usage: as original, or optional rtl parameter, such as

$(function(){
$("ul#ticker02").liScroll({travelocity: 0.05, rtl: true});
});