Show Amazon book reviews on your site with the Product Advertising API

Some months ago I added book reviews from Amazon to My Reading List. Under Reviews, when you click "Amazon" you get an overlay with the 3 most useful reviews. In this post I show you how to do it.

Amazon ECS

featured image

I use Amazon all the time to look if a particular book is worth my time. Hence book reviews had to be in this app. I found a good PHP class to do this easily: Amazon-ECS-PHP-Library. Using this class the app generates an URL to an Amazon page with only the reviews on it (e.g. http://www.amazon.com/reviews/iframe?akid=..more..parameters..), based on its ISBN number. This URL is linked to under Reviews: "Amazon". When clicked, I use Fancybox (discussed previously) to load the URL in an overlay (iframe). See the images below.

Inputs: ISBN (10 / 13 digits) and ASIN

Note that My Reading List uses the Google Books API which returns short and long ISBN numbers (10 and 13 digits respectively, about ISBN). The 10 digit string is usually equal to the ASIN which is the "Amazon Standard Identification Number". If there is no response from the short ISBN/ ASIN, I convert the longer ISBN to ASIN and try with that one. I had to add the "lookupIsbn" method to the Amazon ECS class to be able to do this. With the two ISBN types supported I got a response URL for almost all the books I have tested so far.

Caching

I was considering caching the Amazon iframe URLs, but they are only valid for 24 hours so I decided to generate them on the fly for now.

How it looks

This is the Practical Vim page:

Link to reviews:

link on book page

Overlay when clicked:

reviews in overlay

The code

# see comment above about 10 and 13 digit ISBNs 
if($reviews = getAmazonReviews($book['ISBN_10'])) {
  $amazonReviewsIframe = $reviews;
} else {
  $asin = isbnToAsin($book['ISBN_13']);
  $amazonReviewsIframe = getAmazonReviews($asin); 
}

..

if($amazonReviewsIframe) {
  echo "<a class='fboxEdit' href='$amazonReviewsIframe'>Amazon</a>";
}



(functions) 

# get the first two values from the Product Advertising API, 
# last 2 values are needed for Amazon-ECS-PHP-Library
define("API_KEY",         "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
define("API_SECRET",      "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
define("COUNTRY_CODE",    "com");
define("ASSOC_TAG",       "myrealis");


function amazonApi() {
  # https://github.com/Exeu/Amazon-ECS-PHP-Library/blob/master/lib/AmazonECS.class.php
  require_once 'AmazonECS.class.php'; 
  $client = new AmazonECS(API_KEY, API_SECRET, COUNTRY_CODE, ASSOC_TAG);                                                                
  return $client;
}

function getAmazonReviews($asin) {
  $client = amazonApi();
  $response = $client->category('Books')->responseGroup('Reviews')->search($asin);

  if($response->Items->Item->ASIN == $asin) {
    return $response->Items->Item->CustomerReviews->IFrameURL;
  } else {
    return False;
  }
}

function isbnToAsin($isbn){
  $client = amazonApi();

  # I extended the AmazonECS.class with the "lookupIsbn" function
  $response  = $client->category('Books')->lookupIsbn($isbn);

  if(isset($response->Items->Item->ASIN)) {
    return $response->Items->Item->ASIN;
  } else {
    return False;
  }
}


# added inside the AmazonECS.class
..
public function lookupIsbn($isbn) {
  $params = $this->buildRequestParams('ItemLookup', array(
    'ItemId' => $isbn, 'IdType' => 'ISBN', 'SearchIndex' => 'Books'
  ));

  return $this->returnData(
    $this->performSoapRequest("ItemLookup", $params)
  );
}

How to install Ruby(Gems) as regular user on Bluehost and Unix

In this posts some notes on installing Ruby and RubyGems on Bluehost, my hosting provider. While I was writing this I also tried it on Solaris 11 and Ubuntu 12.10 as a regular (non-root) user. So now I can deploy the latest version of Ruby to most places I push code to.

Of course this only makes sense if you want to have the latest version or if Ruby is not installed at all and you have no sudo / root rights. For Ubuntu I usually have full access so running an "$ apt-get install ruby" would be all it takes. For Bluehost I obviously don't have root access, for Solaris usually the same. Bluehost does come with gem 1.7.2 and ruby 1.8.7 out of the box. Learning Ruby I want to make sure though I can use the latest version on my local- and remote hosts, hence this post.

Installation steps

  • First get the latest version:
  • $ mkdir ~/ruby && cd ~/ruby
    $ wget ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p286.tar.bz2
    (at this time)
    $ bzcat ruby-1.9.3-p286.tar.bz2 | tar -xvf -
    $ cd ruby-1.9.3-p286

  • Install it (see also this article).
  • $ ./configure --prefix=$HOME
    $ make
    $ make install

    !! On Solaris 11 you might need to install gcc ($ sudo pkg install gcc-3) first.

    !! Although this worked perfectly as a user on Ubuntu and Solaris, on Bluehost this failed. On a hosting box it might be better to run the following commands which I found here:

    (still in ruby-1.9.3-p286)
    $ autoconf
    $ nice ./configure --enable-shared --prefix=$HOME/ruby/ 
    $ nice make 
    $ make install
    

    featured image ruby rubygems install

  • Next make sure you update your $PATH variable in your shell's dotfile with the new Ruby binary (in my case .bashrc).
  • If this is your first Ruby installation you can just append it to the end, if you have multiple instances, like I had on Bluehost, you might prepend it so that the shell looks first at the Ruby binary you installed with your user account. For dealing with multiple Ruby versions on your box, you might want to check out Ruby Version Manager as well.

    $ vi ~/.bashrc
    export PATH=$HOME/ruby/bin:$PATH
    

    If you don't want to mess with the PATH variable, you can also create aliases:

    $ vi ~/.bashrc
    alias ruby19='$HOME/ruby/bin/ruby'
    alias gem18='$HOME/ruby/bin/gem'
    
  • Next install RubyGems
  • $ wget http://production.cf.rubygems.org/rubygems/rubygems-1.8.24.tgz
    (at this time)
    $ zcat rubygems-1.8.24.tgz | tar -xvf -
    (gzcat on Solaris)
    $ cd rubygems-1.8.24
    $ ruby setup.rb 
    (where ruby is $home/ruby/bin/ruby due to the PATH change in the previous step)
    

    <my home>/bin/ruby/lib/ruby/1.9.1/yaml.rb:56:in `<top (required)>':
    It seems your ruby installation is missing psych (for YAML output).
    To eliminate this warning, please install libyaml and reinstall your ruby.
    RubyGems 1.8.24 installed
    
    == 1.8.24 / 2012-04-27
    
    * 1 bug fix:
    
      * Install the .pem files properly. Fixes #320
      * Remove OpenSSL dependency from the http code path
    
    
    ------------------------------------------------------------------------------
    
    RubyGems installed the following executables:
      <my home>/ruby/bin/gem
    

    The libyaml error didn't get in the way so far, but usually it is better to solve any errors ... I got this error on each Unix flavor (as a user).

  • Bluehost recommends the following GEM variable settings, again in your shell's dotfile:
  • export GEM_HOME=$HOME/ruby/gems
    export GEM_PATH=$GEM_HOME:/usr/lib/ruby/gems/1.8
    export GEM_CACHE=$GEM_HOME/cache
    (and if you will use rails)
    ENV['GEM_PATH'] = '/path/to/your/home/ruby/gems:/usr/lib/ruby/gems/1.8'
    

    /usr/lib/ruby/gems/1.8 is the default gems location, if this is the first installation just leave this off.

  • To test if it works I install two gems:
  • $ gem install RedCloth
    ..
    
    $ gem install hpricot
    ..
    
    $ gem list --local
    ..
    *** LOCAL GEMS ***
    
    hpricot (0.8.6)
    RedCloth (4.2.9)
    
    $ ls $HOME/ruby/gems/gems
    hpricot-0.8.6  RedCloth-4.2.9
    

    !! Note that on Ubuntu you might get the following error which means you have to install zlib ($ sudo apt-get install zlib1g-dev) :

    ERROR:  Loading command: install (LoadError)
        cannot load such file -- zlib
    
  • Testing the new gem:
  • $ irb
    irb(main):001:0> require "rubygems"
    => true
    irb(main):002:0> require "RedCloth"
    => true
    irb(main):003:0> r = RedCloth.new("this is a *test* of _using RedCloth_")
    => "this is a *test* of _using RedCloth_"
    irb(main):004:0> puts r.to_html
    <p>this is a <strong>test</strong> of <em>using RedCloth</em></p>
    => nil
    

    Works.

And that's it. Now I can write Ruby 1.9.x scripts knowing that they will work on my remote servers because I will simply install the latest Ruby version as my user.

Sharemovi.es / how to dynamically load IMDB ratings for movies

In this post I will show how you can dynamically load omdbapi data into your page. This example only uses the rating info because all other movie data I get already from themoviedb API. I use this feature on sharemovi.es.

How it works

featured image

I was clicking a lot on the IMDB link on movie pages (example movie page). Mostly because the vote count is much higher than TMDB's so it is a good extra reference to see if a movie is worth watching. The code in this post shows you how to load in this type of data, if you use PHP for your own movie site project, you can easily re-use these examples.

The strategy is very similar to the autocomplete in the last article. We ask Javascript (jQuery in this case) to watch for a click on an anchor with class "imdbRating". Upon a click on this element, we replace the parent "span" element with a loader gif image, while calling the omdbapi in the background. When this query comes back with the result, we replace the loader image with it.

The actual coding

  • The html that sets up a span and anchor within it with a class and a unique id. The imdb movie is hardcoded for this example, but in the real app it is dynamically generated from the database (cached movie) or themoviedb API (new movie):
  • ..
    <span><a class="imdbRating" href="#" id="tt0068646">IMDB</a></span>
    ..
    
  • The Javascript that handles the click on the link (include the jQuery library first):
  • $(document).ready(function() {                                                                                                                                                 
      ..
      ..
    
      $(".imdbRating").click(function(){                                                                                                                                           
        var ratingLink = $(this);
        var ratingDiv = $(this).parent();
        var imdbTitle = ratingLink.attr("id");
    
        ratingDiv.html("<img src='i/mini-loader.gif'>"); 
    
        $.post("get_imdb_voting.php", { title: imdbTitle }, function(data){
          ratingDiv.html(data, 
            function(){ 
              $(this).fadeIn(); 
            }
          )
        });
        return false; 
      });
    
      ..
      ..
    });
    

    This watches for the imdbRating class to be clicked and before finishing it returns false to not actually follow the link. It defines the ratingDiv as the parent of the anchor, in this case the wrapped "span" I showed in the html code. It populates this DOM element with the gif loader to show the user we are making progress. It then does a post (ajax) request to the PHP that is going to query the omdbapi. When it does the defined readingDiv (span) will be populated with the result of this, loading it in with a fadeIn jQuery animation.

  • The get_imdb_voting.php script:
  • <?php                                                                                                                                                                          
    if(!isset($_POST['title'])) return -1; 
    $title = $_POST['title']; 
    
    $imdbVoting = getImdbVotes($title);
    if($imdbVoting && $imdbVoting['rating'] != '' && $imdbVoting['votes'] != '') {
      echo $imdbVoting['rating'] . ' (' . $imdbVoting['votes'] ; 
      echo ' <a href="http://imdb.com/title/'.$title.'" target="_blank">IMDB</a> votes)';
    }
    
    function getImdbVotes($title){                                                                                                                                                 
      $imdbApiUrl = "http://www.omdbapi.com/?i=$title"; # title like tt0103759
    
      $results = useCurl($imdbApiUrl);
      $results = json_decode($results);
    
      $imdbVoting = array();
      $imdbVoting['rating'] = $results->imdbRating; 
      $imdbVoting['votes'] = $results->imdbVotes; 
      return $imdbVoting;
    }
    
    function useCurl($url) {                                                                                                                                                       
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $url);
      curl_setopt($ch, CURLOPT_HEADER, 0);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
      curl_setopt($ch, CURLOPT_FAILONERROR, 1);
    
      $results = curl_exec($ch);
      $headers = curl_getinfo($ch);
    
      $error_number = curl_errno($ch);
      $error_message = curl_error($ch);
    
      curl_close($ch);
    
      return $results;
    }
    ?>
    

    This is pretty straight-forward, it uses curl to query omdbapi.com for the title it receives. It uses json_decode to parse the results. At this time I am only interested in the rating and voting which I print out if present. I could do some error handling, but for now I just return no html if nothing comes back. Also omdbapi returns "N/A" if it doesn't find a result which is ok for me.

And that's all that is needed to show omdbapi data dynamically on your page. If you want to use more data of the return result just study the structure of the data, for example: http://www.omdbapi.com/?i=tt0103759 and/or use a "print_r($results);" after the "$results = json_decode($results);" statement to see in Firebug what the post call comes back with.

See this in action

See the process in the featured image of this post or in the following sequence:

matrix movie page
loader when clicking the imdb link
with the results from imdbapi

Sharemovi.es / make a cool multifunctional autocomplete

Today two posts about enhancements in Sharemovi.es. In this post I will show you how to make a multifunctional autocomplete box. In the next article I show you how to dynamically load in IMDB vote results for a particular movie.

Autocompletes are around for a long time, but I still love them for my apps. It makes the app flow faster and navigation can be much shorter not cluttering the page.

I implemented a multi-use autocomplete some time ago for FB Reading List:

fbreadinglist autocomplete

I will show you a similar approach I took for sharemovi.es.

How it works

When you search for "be" it finds movies for that string:

searching for be

But if you prepend the search term with the "@" sign, for example "@be" it starts to find persons (actors/directors) and sharemovi.es users in the same search:

searching for person be

Implementation

This is an extension to the movie search interface with instant trailers I blogged about last year.

Autocomplete plugins work with AJAX: javascript calls a serverside script (PHP in this case) which returns its result in a certain format (JSON in this instance). Javascript digests this result and sticks it into the DOM.

The backend script (search.php) looks like:

<?php
if (!isset($_GET["term"])) {
	return;
}

include 'libs/functions.php';

$term = $_GET["term"];
$orgTerm = $term;
$lang = 'en'; # for now
$return_arr = array();

# limit size of autocomplete window
$maxNumItems = array();
$maxNumItems['movies'] = 5; # 5 items for movies because the content is big
$maxNumItems['persons'] = 4; # persons can add up to 7, 4 for api, 3 for users
$maxNumItems['users'] = 3; 
$counter['movies'] = 0; 
$counter['persons'] = 0; 
$counter['users'] = 0; 

# formatting in JS: http://stackoverflow.com/questions/6070142/jquery-ui-formatting-the-autocomplete-results-add-image-possible

# searches starting with @ search for persons (actors / directors from the API) or sharemovies users 
if(preg_match('/^@/i', $term)){
  $term = preg_replace('[email protected]*/i','',$term);
  $term = urlencode($term);

  # 1. persons
  $results = queryApi3('','','search/person', $term, '', $lang);

  if(! empty($results->results)) {
    foreach($results->results as $person) {
      if ($person->adult == true || $person->profile_path == '') continue; 
      $counter['persons']++; 
      $row_array['type'] = "person";
      $row_array['id'] = $person->id;
      $row_array['value'] = $person->name;
      $row_array['img'] = $POSTER_SMALL . $person->profile_path;
      $row_array['slug'] = createSlug('person',$person->id,$person->name);

      array_push($return_arr,$row_array);
      if($counter['persons'] == $maxNumItems['persons']) {
        break;
      }
    }
  }
    
  # 2. users in db
  include "libs/db_connect.php";
  $term = $mysqli->real_escape_string($term);

  $search_term = str_replace(array("+"), " ", $term);
  $q = "SELECT shmov_users_movies.id,shmov_users.name,shmov_users.ins as joined,count(shmov_users_movies.id) as total "; 
  $q .= "FROM `shmov_users_movies` join shmov_users on shmov_users_movies.id=shmov_users.id ";
  $q .= "WHERE LCASE(shmov_users.name) like '%$search_term%' ";
  $q .= "group by shmov_users_movies.id order by total desc";

  $r = $mysqli->query($q);
  if($r->num_rows) { 
    while($row = $r->fetch_object()){
      $counter['users']++;
      $row_array['type'] = "user";
      $row_array['id'] = $row->id;
      $row_array['value'] = $row->name . " (user)";

      $row_array['total'] = "<small>" . $row->total . " movie" ;
      $row_array['total'] .= ($row->total != 1)? "s": "" ; 
      $row_array['total'] .= " on watchlists";
      
      $row_array['joined'] = " / Joined " . timeAgo($row->joined) . "</small>";

      $row_array['img'] = "https://graph.facebook.com/".$row->id."/picture?width=40&height=40";
      $row_array['slug'] = "user/".$row->id;

      array_push($return_arr,$row_array);
      if($counter['users'] == $maxNumItems['users']) {
        break;
      }

    }   
  } 

  # if nobody found return error message (see JS: it expects a message to start with Oops)
  if(empty($return_arr)){
    array_push($return_arr, "Oops ... nobody found for $orgTerm"); 
  }


} else {

  # else = movie title search
  $term = urlencode($term);
  $results = queryApi3('','','search/movie', $term, '', $lang);

  if(empty($results->results)) {
    # if nobody found return error message (see JS: it expects a message to start with Oops)
    array_push($return_arr, "Oops ... no movies found for $orgTerm"); 
  } else {
    foreach($results->results as $movie) {
        $counter['movies']++; 
        $row_array['type'] = "movie";
        $row_array['id'] = $movie->id;
        $row_array['value'] = ucwords($movie->title); 
        if($movie->original_title && ($movie->title != $movie->original_title)) {
          $row_array['org'] = ucwords($movie->original_title); 
        } else {
          $row_array['org'] = '';
        }
        $row_array['pop'] = $movie->popularity;
        $row_array['rating'] = $movie->vote_average . " (". $movie->vote_count." votes)";
        $row_array['plot'] = get_n_words($movie->overview, 15) . " ..." ;
        $row_array['year'] = substr($movie->release_date, 0, 4);
        $row_array['img'] = $POSTER_SMALL . $movie->poster_path;
        $row_array['slug'] = createSlug('movie',$movie->id,$movie->title);

        array_push($return_arr,$row_array);
        
        if($counter['movies'] == $maxNumItems['movies']) {
          break;
        }
    }
  }
}

echo json_encode($return_arr);
?>

featured image

  • The search term is sent from JS, if it starts with @ it performs a search against themoviedb and against the users in the sharemovies database. If no @ is prepended to the search, it does a regular movie search, again against themoviedb API. I could cache the results but for now the search box use seems within the themoviedb limits (I do cache movie and person pages though which reduces the amount of calls to the API significantly).
  • The $maxNumItems array restricts the number of items in the returned result so that the autocomplete result box fits reasonably well within the page.
  • The json_encode is run on the array of row arrays for JS consumption.
  • queryApi3 is a routine to query themoviedb API, I omitted it for brevity, interesting in more details, just drop me a line ...

The Javascript code to digest the result (after including the jQuery library):

$(document).ready(function() {

  ..
  ..

  // prevent autocomplete field from being submitted on enter
  // actually ace's solution worked here: http://stackoverflow.com/questions/7237407/stop-enter-key-submitting-form-when-using-jquery-ui-autocomplete-widget
  $("input#autocomplete").keypress(function(event){
    var keycode = (event.keyCode ? event.keyCode : event.which);
    if (keycode == '13') {
      event.preventDefault();
      event.stopPropagation();    
    }
  });


  $("input#autocomplete").autocomplete({	
    search: function(event, ui) { 
      $('.spinner').show();
    },
    open: function(event, ui) {
      $('.spinner').hide();
    },	
    source: "search.php",
    minLength: 2,
    select: function(event, ui) { 
      $('.spinner').show();

      var currentUrl = document.URL;
      var baseUrl; 

      if( currentUrl.match(/sharemovi.es/) ){
        baseUrl = "http://sharemovi.es/";
      } else {
        baseUrl = "http://127.0.0.1/sharemovies/";
      }
      var redirectTo = baseUrl + ui.item.slug;

      location.href=redirectTo; 

    }
  }).data( "autocomplete" )._renderItem = function( ul, item ) {
    if(item.value.match(/^Oops/)) {
      return $( "<h2 class='notfound'></h2>" )
      .data( "item.autocomplete", item )
      .append( "<p>"+item.value+"</p>" )
      .appendTo( ul );

    } else {
      var movieInfo = '';

      movieInfo += "<a id="+item.id+">";
      movieInfo += "<img src='" + item.img + "' />";
      movieInfo += "<span><h4>" + item.value+ "</h4>";
      if(item.type == "user"){
        movieInfo += item.total;
        movieInfo += item.joined;
      }

      if(item.type == "movie"){
        movieInfo += "<small><p>";
        if(item.org) movieInfo += "Original Title: "+item.org+"<br>";
        movieInfo += "Release Year: "+item.year;
        movieInfo += "<br>TMDB Rating: "+item.rating;
        movieInfo += "<br><small>Popularity: "+item.pop;
        movieInfo += "</small></p>";
        movieInfo += "<div id='"+item.value+' '+item.year;
        movieInfo += "' class='trailer'>";
        movieInfo += "<img src='i/youtube.png'>";
        movieInfo += "</div></small>";
      } 

      movieInfo += "</span></a>";

      $( "<li></li>" )
        .data( "item.autocomplete", item )
        .append(  movieInfo )
        .appendTo( ul );

      // if search is for person or user, the autocomplete box needs to be smaller
      if(item.type == "user" || item.type == "person") {
        $(".ui-autocomplete li").css("height", "50px");
        $(".ui-autocomplete li a").css("height", "50px");
        $(".ui-autocomplete li a img").css({"height": "40px", "width": "40px"});
        $(".ui-autocomplete li a span").css("left", "50px");
      } 

      return false; 
    }
  };

  ..
  ..
  // more sharemovies JS code ..
  ..
  ..

});
  • source: "search.php" => here you see the backend PHP script being called.
  • .data( "autocomplete" )._renderItem => this consumes the elements returned by the PHP backend script. It wraps it in convenient HTML I styled with CSS (which exact styling is beyond the scope of this tutorial).
  • Note in the last part I overwrote some of the CSS rules of the autocomplete jQuery plugin to present smaller entries for person vs. movie results (see printscreens at the start of this post). The movie results give back more verbose data. So now each result takes up just the necessary space.
  • Some things are hardcoded like the sharemovi.es domain name and the handling of no results ("item.value.match(/^Oops/)") but that is fine for now.

At last the form html that has the DOM element Javascript acts upon:

<form action="#"  id= "searchForm" method="post">
  <p class="ui-widget">
  <input type="text" id="autocomplete"  name="autocomplete" class="defaultText" 
    title="Search movie ... [ use '@' for actors/directors/sharemovies users ] "/> 
  </p>
</form>

This tutorial gives you a 90% jumpstart how to implement a rich multi-functional autocomplete. I hope it inspires you to build something similar. Also let me know if you have implemented a similar feature in any other language.

PHP Application Development with NetBeans Beginners Guide

At least 3 years ago NetBeans started to support PHP. PHP Application Development with NetBeans gives a nice tour of how to simplify development with this IDE.

Disclaimer: I received a copy from Packt for review.

featured image

I haven't seen any title so far that specifically deals with PHP development in NetBeans, in that sense it filled a gap (amazon search). NetBeans has been around for quite some time now. It was one of my favorite IDEs some years ago, but I gradually moved to TextMate, then to Sublime Text, and lately I exclusively use Vim. Yes, these are text editors, which can be expanded to full IDEs. Honestly, I haven't used a full-blown IDE in a while so this was an interesting book to see what's supported these days. This book shows interesting examples in that regard:

Contents:

  • Chapter 1: Setting up your Development Environment
  • Chapter 2: Boosting Your Coding Productivity with the PHP Editor
  • Chapter 3: Building a Facebook-like Status Poster using NetBeans
  • Chapter 4: Debugging and Testing using NetBeans
  • Chapter 5: Using Code Documentation
  • Chapter 6: Understanding Git, the NetBeans Way
  • Chapter 7: Building User Registration, Login, and Logout
  • Appendix A: Introducing Symfony2 Support in NetBeans 7.2
  • Appendix B: NetBeans Keyboard Shortcuts

I didn't have time to check if the code examples worked on my machine, but I did like the way the author structured and explained them. The book starts with quite some installation screenshots, these could have been moved to an appendix probably. However, when coding started (ch3) the book's pace speeds up and nice features like debugging, building and automating tests make it an interesting read. After all I felt the book reached a good balance between GUI options, programming and development.

One annoying thing was that the code in the epub version of the ebook was not indented. So I switched to the PDF version which was perfect. I guess the printed edition is like the PDF so that should not be an issue.

PHP Application Development with NetBeans is recommended reading if you want to explore the NetBeans IDE. Even if you like to work in the terminal like me this book is a good exploration what an IDE can offer you. It is always good to keep analyzing your tools. I can even foresee a mixture of IDE and editor use in my case.

The PHP and JS code is good, well explained, but I think the takeaway is the easiness with which NetBeans facilitates Unit Testing, Version control, writing PHP documentation, Debugging, etc. In 250 pages a lot is covered, some things only at the surface (debugging for example), but that is inherent to a beginner's guide.

I think the book meets its goal: show how PHP development could be simplified in this IDE.

book cover

The book I wished I had read when coding my first PHP project

I finished reading PHP & MYSQL Novice to Ninja and I recommend it for beginners and intermediate learners of PHP. It is easy to digest, teaches best practices and the code is clean and usable.

featured image

Disclaimer: contrary to other book reviews, I did not get a copy for this one. As I found this a remarkable PHP book, I wanted to dedicate a blog post to it.

The author probably can describe best what kind of book this is (source): " ... Just as PHP and MySQL have grown from the young upstarts of the web development world into mature, stable platforms for billion-dollar businesses, this book that I've been writing again and again for over a decade has grown up … It's time to write PHP like the big kids do ...".

Why is this such a good starter for learning PHP?

  • This is the 5th edition of what has become a best-seller book on PHP. Each edition has grown in handling more recent PHP versions and ways PHP is used nowadays. It uses a clean MVC (model-view-controller) structure seperating PHP controlling code from HTML view templates. It attacks common security pitfalls and presents the concepts in a logical order. From reading the 4th and 5th editions I can attest that it is a well crafted book.
  • Beginnings of OOP: the database code has been rewritten in this edition to use PDO (PHP Data Objects API) which professional PHP developers should use. PDO is like JDBC (Java) or DBI (Perl). It provides a data-access abstraction layer which makes changing from one database server to another easy. PDO works with prepared statements which protect you from SQL-injection attacks. Required is a database-specific PDO driver ... but I get sidetracked ... ;)
  • Code best practices: apart from PDO, we see try/catch blocks, MVC architecture, eye for security practices (for example the use of is_uploaded_file when dealing with file upload forms) and more
  • I found the database chapters amazing. Again clear, concise, and teach good practices in design. It is mixed in with the PHP chapters: ch2 gives you some basics about mysql, ch5 explains Relational Database Design, ch6-9 uses it heavily in the building of the internet joke database CMS, ch10 - MySQL Administration and ch11 - Advanced SQL Queries, teach more advanced topics. Plus 3 appendices with MySQL reference material. Even if you are going to use another database product then MySQL, the database intro alone provided in this book is worth spending the money.
  • Very practical: this book shows you how to build your first dynamic database driven website, step by step. The examples are very clear. I got some years of experience now so the first 100 or so pages I just gazed over, but the project code still had good insights for me. So even having the basics, this can still be a useful reference to do things the right way.

The Sitepoint podcast interviewed the author some months back about this new 5th edition. To go deeper, I think PHP Master might be a good sequel (I have to read it yet). It explains more advanced topics like OOP, testing, security and APIs.

cover php mysql novice to nina

Intermediate Perl by Schwartz/Foy/Phoenix; O'Reilly Media

After having learned the basics in Learning Perl ("Llama book"), Intermediate Perl takes you to the next level. It is aimed at doing more serious development work (programs of over 1000 lines of code). Hence it introduces the more advanced concepts as complex data structures, references, packages and OOP.

Structure

featured image

Disclaimer: I received a copy from O'Reilly for review.

This book covers a lot in its 21 chapters. It serves well a classroom or self-paced learning discipline with equally length chapters of approximately one hour reading time and exercises that help you practice the newly introduced concepts.

I like the introductional chapters, getting into CPAN, intermediate foundations (grep, map, eval) and references. Good prep work to start tackling more complex problems. Up until chapter 10 it expands on references concepts which was a really good read. At chapter 11 there is a turning point into software development: how to write bigger and more maintainable programs in Perl. How to create your own Perl distribution, Perl's OOP design and (Advanced) Testing, ending with Moose and contributing to CPAN. At this point, when using all these concepts in your daily work, you are probably becoming a master. The next logical step is to then read Mastering Perl, the 3rd O'Reilly Perl learning series book or the Perl bible, Programming Perl.

OOP Perl

I found this the most complex part of the book, not as clear as the other topics (ch 1-11 and Testing). I am not sure this is inherent to Perl's design of OOP or the way Intermediate Perl presented the content and/or its samples. I probably have to start using OOP more in my work, and I am going to read Damian Conwey's OOP title on the subject which seems a good place to learn OOP Perl in depth.

Overall opinion

I can recommend Intermediate Perl as a logical step after the Llama. If you want to build larger, more powerful/ robust programs, this gives you a jumpstart. You might as me need more material and practice on object oriented Perl but that is fine, this is still a very useful reference that brings together fundamental intermediate to advanced Perl techniques.

If you are serious about a career in Perl development this book should be on your shelf. For simple scripting needs it might be overkill, but even then you will need to pass a reference around form time to time, re-use software from CPAN, etc. Apart from that, you will learn better, faster, and more efficient coding practices in Perl from this book.

intermediate perl book cover

Book review - Practical Vim: Edit text at the speed of Thought

Practical Vim: Edit text at the speed of Thought is an intermediate to advanced book on Vim published by The Pragmatic Bookshelf. I found this a very useful Vim resource to sharpen my skills. In this post some more feedback on the book.

(Disclaimer: I received a copy of Practical Vim for review)

Overview

This book teaches a lot of Vim skills in 121 tips. Some are easy, others advanced, but above all they teach the Vim way! Following the examples you start to approach text edit challenges differently. Even feeling comfortable with most editing tasks, there is probably a better way to do it. This book had quite a few eye-openers for me, for example the Dot Formula. The examples are based on real-life scenarios, and compare different approaches, explaining why one is better than the other. The examples are also very well illustrated with tables showing how the Buffer Content looks after one or more keystrokes.

Mode of use

Cover-to-cover read, or as reference guide, both approaches work. Practicing the examples in Vim while reading is recommended, it helped me memorizing the keystrokes better. Apart from that, as noted here, changing 100% to Vim for all my projects and text editing has been a great help to get better.

I learned a lot

I added quite some things to my standard repertoire: diw, df, dt, use s instead of x + i, ctrl+r to paste a register, the "+ to copy/paste to/from OS clipboard, macros, better navigation between words (w b e ge vs W B E GE), CTRL-O and CTRL-I to navigate the jumplist (across open files), nmap ,c :%s///gn in .vimrc as a shortcut for number of search hits, make power searches, better use of regex, and more ... A lot of good stuff, but I am glad with the tips structure, so I can easily go back and commit more to my muscle memory (it is all about making it a habit, see Seven habits of effective text editing).

Two more things I liked about this book: 1. Practical Vim shows you how to get by with Vim’s core functionality (almost no plugins), 2. a lot of links to Vim's Built-in Documentation and external resources.

I think this book and the author's screencasts are both unmissable to become proficient in Vim.

practical vim book cover

Perl practice: daily mail of all movies aired on Spanish TV

I like to solve common day problems, at the same time learning more coding. Learning to code is all about practice. Today I show you a Perl script to get a daily email listing all movies broadcasted by Spanish TV in the coming 24 hours.

featured image

I tried to do this some time ago - BeautifulSoup is very powerful for html parsing, but with the typical tv guide I had to rely on a class "cine inactiu" to filter the movies out. This worked pretty well, but it was not complete and as the movie names are in Spanish I linked to imdbapi for more info which usually did not yield any result. So I needed to redo this exercise ...

I didn't start scripting. First I looked for a better source which I found. This page does half of the work finding all movies to be aired today on Spanish TV. Usually it is not necessary to code everything! What I wanted to add on top of this was:

  • Enrich this list with info for each movie, mainly the director and actors. This was easy following and parsing each link of the list.
  • Email this list to my email with a cron job. An issue was the encoding not showing up well in Apple Mail and mailx not sending out the cron mail when the encoding was not defined well in the command's switches. More on that in a minute, without further ado the script which is also available on Github:
#!/usr/bin/perl -w
# author: bob belderbos
# v0.1 sept 2012
# purpose: send an email with all movies on Spanish TV in the next 24 hours
#          sincroguia.tv has a pretty complete list
#          this script servers best in a daily cronjob
#
use strict; 
use Data::Dumper; 
use LWP::Simple;
use Encode qw(encode decode); # http://perlgeek.de/en/article/encodings-and-unicode

my $enc = 'utf-8'; 
my $output;
my $email = "[email protected]";

my @html = getUrl("http://www.sincroguia.tv/todas-las-peliculas.html");

# movie lines start with hh:mm timestamps
for (grep {/^d{2}:d{2}|^<br/} @html){
  # separate days
  if(/^<br/){
    s/<br />//g;
    $output .= createHeader($_, "*");
    next;
  }

  # parse movies
  m/(d{2}:d{2}) - <a.*?"([^"]+)" href="([^"]+)".*- ([^<]+).*/;
  my ($time, $title, $url, $channel) = ($1, $2, $3, $4);
  $output .= encode($enc, 
    createHeader("$time / $channel / $title") . 
    "$urln" . 
    getMovieInfo($url) . 
    "nn");
}

# send me the generated movielist
sendEmail($email, $output);
  


sub getUrl {
  my $url = shift; 
  my @html = split /</?li[> ]/, get($url);
  return @html;
}


sub getMovieInfo {
  my $url = shift; 
  my $info;
  for(getUrl($url)){
    next if(! /column/);
    for my $line (split /</?h3[> ]/, $_){
      if($line =~ /Director:|rpretes|Idioma|Nacionalidad|A&ntilde/){
        $line =~ s/.*?strong>(.*)</strong>(.*)/$1$2n/g;        
        $line =~ s/A&ntilde;o/Estreno/g;
        $info .= $line ;
      }
    }
    last; 
  }
  return $info;
}  


sub createHeader {
  my $str = shift; 
  my $delimiter = shift // "=";
  my $width = 70;
  my $output = "n" . $delimiter x $width . "n" . $str . "n" . $delimiter x $width . "n";
  return $output;
}


sub sendEmail {
  my ($to, $output) = @_;
  my $subject = "Today's movies Spanish TV";
  
  # on mail pipe: http://objectmix.com/perl/380680-sending-email-perl-using-pipe-mailx.html
  open my $pipe, '|-', 'mailx', 
    '-s', $subject, 
    # char issue mailx: http://forums.opensuse.org/english/other-forums/development/programming-scripting/419802-charset-problem-mailx.html
    '-S', 'ttycharset=utf-8', '-S', 'sendcharsets=utf-8', '-S', 'encoding=8bit', 
    $to or die "can't open pipe to mailx: $!n";
  print $pipe $output;
  close $pipe;
}

Some notes / things I learned

  • I learned how send email via Perl, see the "sendEmail" subroutine. I used a pipe / mailx, using sendmail would not work with my hosting account. The only thing I had trouble with were Spanish characters like é, á, ñ, etc. Mailx would complain, not send out the mail, putting the email in dead.letter in my home. Encoded to utf-8 the help of this article did the trick!
  • example mail

  • The other challenge with Spanish characters was parsing the source page. I found a good article explaining encodings and code to do this.
  • The rest is pretty basic Perl, some Regex that really makes me like Perl and use it for more and more text parsing. I probably have to use some eval {} if one of the movie pages does not respond, otherwise the cronjob will send me another mail with the stderr output of the script execution (or I can say 2>/dev/null in the cronjob, but I think script should handle this).

Bonus

Having the time coded as hh:mm, Apple mail recognizes this and when clicking it you can add an event to your calendar. This way I can easily put a reminder of a movie I potentially want to see later in the evening.

add event from mail

Update 23.09.2012

The movie page does not always give the English movie name and sometimes it has a generic term like "La película de la semana" which doesn't give any clew. So I did some more parsingto include the movie name, original name and plot info. I also included the Twitter and Facebook sharing links which are on each movie page at the bottom, example:

new output

5 tips that helped me becoming faster in Vim

Learning Vim takes time, but once you master enough commands (keys), you understand why it is one of the most powerful editors out there. In this post some simple tips that helped (and still helps) me improving my Vim skills.

Practice

OK, like becoming a better programmer practice is everything, but to facilitate that you need to get out of your comfort zone. I used Textmate a lot on the mac and recently I was using Sublime for all my coding on each OS. If you are serious about learning Vim though, start using it for all your work. At the beginning you will not be able to do all things as fast as in your favorite (graphical) editor but that pushes you to learn the shortcuts / commands, and believe me: with Vim you can do it all! I think this is the most important step towards becoming a Vim ninja.

Start looking up key shortcuts / commands / combinations

featured image

This advice comes from destroy all software: when you don't know about the functionality of a certain key shortcut, look it up with :help . Go from unshifted keys, to shifted keys, to control keys as this is the order of importance of the keys. Check out the motion commands (:help motion.txt) as well. An important rule is to never repeat yourself, when you do it probably indicates a hole in your Vim knowledge (cheers Gary for this great piece of advice).

You can't remember all shortcuts in a week, but gradually over time you can learn quite a lot. Just by doing this I found out about f+char to go to the next char (so you can delete a phrase till the end dot by df.) , markers with 'm', named buffers, using w or e to go to next words including/excluding trailing spaces, and other less frequently used shortcuts. It is also important to learn useful combinations like diw (delete in word), or daw = deleting a word killing the adjacent white space as well, or copying or deleting till the end of the line, y$ and d$ respectively. Another very useful thing to try to grasp early on is repeating commands: . for repeating a command in normal mode, n/N for forward/backward repeated search and & to repeat a replacement (:s/foo/bar/g) operation.

Check out some books / resources about Vim

Even with some prior knowledge I found $ vimtutor a useful program to run and doublecheck, it takes +/- 20 min and towards the end there are some neat tricks. If you are at a beginner level or you have not used Vim in a while this is the best place to start (refresh).

Books: I am using Hacking Vim and Learning Vim to study and practice. They are invaluable. Learning Vim goes from basic to pretty advanced, I am halfway through and it is a really good resource. Hacking Vim is a cookbook and has tought me some useful tricks so far: cntrl+n for autocomplete, gd on a variable name to go to its initial definition (go to definition), .vimrc settings (see next), % to go to the end of a code block, g, / g; for go back of recent locations you made changes, g# / g* for search for the word your cursor is on, etc. It even introduces to Vim scripting!

Resources II) Google, stackoverflow and blogs

Yes everything is possible in Vim, for example I wanted to open multiple files, no problem : vim -p files and you open files in tabs, ct takes you to the next tab, in horizontal split? No problem, use vim -o files, or when editing a file, open a 2nd one in vertical split with ex command :vsp file2 - most I found by just googling and on stackoverflow. There is so much material out there, and practicing it bit by bit, making notes and doing it over and over again, you become faster and faster in Vim.

Make life easier with .vimrc

Try to tweak your config, I started pretty basic with just a few settings: set nu to set line numbers, mapleader for command-t you really should check out (also discovered this via the very useful destroy all software Vim screencasts), highlighting, etc. I probably end up with 100+ lines over time, but this small set of config settings is helpful already:

syntax on
let mapleader = ","
set nu
set cursorline
highlight CursorLine guibg=lightblue ctermbg=lightgray
set hls is
" <Ctrl-l> redraws the screen and removes any search highlighting.
nnoremap <silent> <C-l> :nohl<CR><C-l>

More Vim ...

I opened a category for Vim to share more tricks and resources in the future. Efficient use of a text editor is one of the fundamental tools of any software developer.

Your editor?

Would Vim be your editor of choice? Why or why not?