• October 28th, 2007

    Fluent: PHP Data Types as Fluent Objects

    Introducing Fluent, a set of PHP 5 classes that make writing code happier. Fluent enables you to deal with data types as objects in the fluent api style, like you can in most other modern programming languages.

    // JavaScript
    "Hello There".toUpperCase().split(' ');
    
    // C#
    "Hello There".ToUpper().Split(new Char[] {' '});
    
    # Ruby
    "Hello There".upcase.split(' ')
    
    # Python
    "Hello There".upper().split()
    
    // PHP
    explode(' ', strtoupper('Hello There'));
    
    // PHP using Fluent
    S('Hello There')->toupper()->explode(' ');
    

    Fluent Data Types

    OMG! “Strings!”

    Using the Fluent_String class you can chain your string manipulation functions together on to one line, much like a sentence.

    // Trim the white space from the left and right; Set to title case;
    echo S('    fluent data type, huzaa!!   ')->trim()->ucwords();
    
    // PRINTS: "Fluent Data Type, Huzaa!!"
    
    // The same as above, but replace whitespace with 3 exclamations
    echo S('    fluent data type, huzaa!!   ')->trim()->ucwords()
    	->preg_replace("/s+/",'!!!');
    
    // PRINTS: "Fluent!!!Data!!!Type,!!!Huzaa!!";
    

    Neat, simple, arrays

    The Fluent_Array class works just like the above string class. Splice, shift, count, asort, whatever, at will.

    echo A(array('Bush','Clinton'))->splice(2,0,array('Bush','Clinton'))
    	->push('Please stop now.');
    
    // PRINTS: "Bush, Clinton, Bush, Clinton, Please stop now."
    
    echo A(array('Bush','Clinton'))->splice(2,0,array('Bush','Clinton'))
    	->push('Please stop now.')->unique();
    
    // PRINTS: "Bush, Clinton, Please stop now."
    

    Numbers…

    Same goes for numbers, except that both int and float are handled within a single class: Fluent_Number.

    // Find the circumference of a circle which has a radius of 3.5cm
    echo N(3.5)->times(2)->times(M_PI);
    
    // PRINTS: 21.991148575129
    
    // Find the radius of a circle based on circumference
    echo N(21.991148575129)->divide(M_PI)->divide(2)->round(1);
    
    // PRINTS: 3.5
    

    Boolean === Complete

    To round it out, the Boolean data type is also supported. This is in place for completeness, so that when you’re dealing with a Fluent object, you’re always returned a Fluent object.

    echo B(false);
    
    // PRINTS: false
    

    Core Data

    You might have asked by now: “what if I want the actual piece if data instead of the Fluent object?” Well, the data at the core of every object is always available to you via the ‘value’ property of the Fluent object, so it’s easy to leave the object behind and pass on the actual data.

    echo O(5)->value;
    
    // PRINTS: 5
    

    Caveats

    There are, of course, some caveats that could trip you up, especially when dealing with numbers. For instance, if you try to directly add to Fluent_Number objects you’ll be given an E_NOTICE, the objects will be incorrectly ‘added’, and you’ll be sent on your way:

    echo $bad = N(10) + N(20);
    Notice: Object of class Fluent_Number could not be converted to int
    
    // PRINTS: 2
    

    Uncaveat

    The correct way is to use operators on the value of an object is to directly work on the core data of the object:

    echo $good = N(10)->value + N(20)->value; // return type = int
    echo $good = N(10)->plus(20);             // return type = Fluent_Number
    
    // PRINTS: 30
    

    Transmogrify that shit

    It’s very common that a single piece of data will need to be different types of data at different points in your application. Fluent objects automatically and intelligently convert your object from one Fluent Data Type to the next based on the data type of the return value, so you won’t have to create new Fluent objects whenever your data changes form:

    echo O('President Bush is an evil bigot.')      // string
    	->explode(' ')                          // array
    	->splice(1,1,array('Cheney'))           // array
    	->join(' ');                            // string
    
    // PRINTS: President Cheney is an evil bigot.
    
    echo O('President Bush is an evil bigot.')      // string
    	->explode(' ')                          // array
    	->splice(1,1,array('Cheney'))           // array
    	->join(' ')                             // string
    	->rev();                                // string
    
    // PRINTS: .togib live na si yenehC tnediserP
    
    echo O('President Bush is an evil bigot.')      // string
    	->explode(' ')                          // array
    	->splice(1,1,array('Cheney'))           // array
    	->join(' ')                             // string
    	->rev()                                 // string
    	->len()                                 // int
    	->times(19.5)                           // int
    	->plus(3);                              // int
    
    // PRINTS: 666
    

    Oh PHP, you’re so crazy.

    I can’t really say that PHP was the most well thought out thing in the world, and that’s probably because it wasn’t really thought out. PHP has adapted as a language as the web has grown and changed, which has enabled people of all skill level to get right in and create amazing tools. The language is incredibly simple but capable of astounding complexity.

    This, of course, has its down side: when it comes to function names, function argument order, and function return values some crazy ass shit went down.

    Fluent objects keep those unsightly stretch marks out of view by automatically shortening function names to their succinct names, among other things.

    array_splice(), your name sucks

    If you’ve been looking closely you’ll have noticed that instead of strtoupper(), strrev() and array_splice() we’ve been using splice(), toupper() and rev(). This feature is universally applied to all functions with name prepended by ’str’, str_’ and ‘array_’.

    What kind of order is this?

    Another incredibly annoying thing about PHP is that there seems to be no logic to how the order of arguments was applied accross functions. The subject of the function, depending on the function, could be either the 1st, 2nd, or 3rd argument passed in.

    With Fluent objects, the subject of the function is always the data within the Fluent object itself. This means that when you’re reading the documentation at php.net, remember that you should refrain from passing the subject as an argument… just pretend it doesn’t exist.

    // Normal sprintf
    echo sprintf('Hello %s.','There');
    
    // Fluent sprintf
    echo S('Hello %s.')->sprintf('There');
    
    // Multi-chained Fluent sprintf
    echo S('Hello %s.')->sprintf('There %s')->sprintf('Sucka');
    
    // Normal str_replace
    echo str_replace(')','(','Sad :)');
    
    // Fluent str_replace
    echo S('Sad :)')->replace(')','(');
    

    What are you looking at?

    What makes Fluent objects so valuable is our ability to chain function calls together indefinitely. In order to do this we always need to have a handle on what we’re dealing with. PHP throws us for a loop here because many of the array functions require that the subject array be passed in as an argument by reference, using the & operator. This then modifies the array you passed in, and returns a result, which is in many cases not the modified array but a boolean “success” indicator, a portion of the original array, the length of the resulting array, and so on.

    Our Fluent objects solve this problem by always returning the modified array while at the same time populating the native property of the Fluent object with the value usually returned by the native PHP function.

    $fruit = array('apple','orange','kiwi');
    $result = array_push($fruit,'banana');
    
    var_dump($fruit);
    
    // PRINTS: array(4) {
    //   [0]=>string(5) "apple"
    //   [1]=>string(6) "orange"
    //   [2]=>string(4) "kiwi"
    //   [3]=>string(6) "banana"
    // }
    
    var_dump($result);
    PRINTS: int(4)
    

    Compare that to how a Fluent object behaves:

    $fruit = A(array('apple','orange','kiwi'))->push('banana');
    
    var_dump($fruit->value);
    
    // PRINTS: array(4) {
    //   [0]=>string(5) "apple"
    //   [1]=>string(6) "orange"
    //   [2]=>string(4) "kiwi"
    //   [3]=>string(6) "banana"
    // }
    
    var_dump($fruit->native->value);
    
    // PRINTS: int(4)
    

    Shorter is Better

    I created these classes to make programming in PHP quicker and more elegant. For this reason you don’t have to use new Fluent_String() to start using the string class. You can if you want, of course, but each data type already has its own one letter shortcut function: A(), S(), N(), and B().

    There is also one more shortcut function for when you don’t care what you’re passing in, you just want it to be a Fluent object: O(). This function will take whatever it is you give it and return the appropriate Fluent data object.

    And…

    That’s basically it, really. Please download it and try it out. If you do, let me know what you think, where it could be improved upon, and if there are any stupid bugs that I missed.

    In the future I plan to deal with a couple outstanding issues. First I’ll update Fluent to allow you to provide your own data type classes to use instead of mine (or extended from mine). Also, an important part of proving the usefulness of Fluent will be to figure out how performant they are. So fairly soon you should be able to expect some benchmarks to compare function calls to their native incarnation.

    Posted in Code/Projects, PHP | 6 Comments »

6 Comments »

  1. jen zwick  |  October 30, 2007   1:35 am

    “Shorter is Better” = I’m better!

  2. Dave Curry  |  October 30, 2007   9:15 am

    That’s awesome Dan, thanks!

    And welcome to the Blogoshpere!

  3. Nate  |  October 30, 2007   9:16 am

    And so begins his domination of the world.

  4. Avram Eisner  |  October 30, 2007   9:17 am

    Dan… this is truly rad!

  5. josh  |  June 3, 2009   6:36 pm

    That’s some ugly syntax.

  6. Dan Dean  |  June 5, 2009   12:08 am

    Hi Josh! Thanks for the kind words and helpful insight. You’ve really outdone yourself.

Leave a comment





Un-Dumbify