Page 1 of 1

Ruby / JQuery Abstraction layer for PHP

Posted: Thu Nov 23, 2006 3:14 pm
by skruby
Hey guys. Let me know what you think of this concept project that i've been working on for the past week.

It is a framework to make PHP more OO in a dynamic way.

Here is what the framework can do

Code: Select all

while (p(10)->times()) { echo 'Echo me 10 times'; }
p() is like the $() in JQuery.

$str = '   he MAN leet  ';

pp($str)->trim()->capitalize_words(); 
echo $str; // output: 'He Man Leet'

The difference b/w p and pp is that pp is passed by reference whereas p is pass by value. 

So. the above is the same as: $str = p($str)->trim()->capitalize_words()->spit();

Spit() will return the variable after processing it.
The reason i came up with this concept was because I was sick of checking the PHP website to see if it's

strstr($email, '@') or strstr('@', $email) and str_replace() or strreplace() etc. You get the idea. The inconsistency.

So now, with this lib, i can do this instead

Code: Select all

p($email)->has('@') and pp($str)->replace('okok');

That's for strings. For objects, you can do this.

p($obj)->get_class();
p($obj)->get_interfaces();

For arrays

pp($arr)->pop()->shift()->append('hello')->prepend('world);

And for shortcuts,

instead of

if (!isset($arr['abc']))
{
   $arr['abc'] = 'some value';
}

you can do this one-liner: pp($arr)->set_default('abc', 'some value');
How this works is that in my lib, there are a lot of modules. Each module has an accept() function to test if the variable passed to the p() or pp() functions can be used with that module. Its like a bastardized version of the Visitor pattern.

Other potential uses

Code: Select all

echo pp($str)->trim()->capitalize_words()->to_leet(); // output: 'H3 Man L33t'

echo p('goog')->get_stock_quote(); // Fetch stock quote from Yahoo

echo p('hello')->to_french(); // output: 'bonjour'

echo pp($email)->js_encode(); // Output: Javascript encoded email
So what do you think? It's more towards JQuery style than Ruby. But I name it Pruby nevertheless becos it's cooler :)

To see all the methods available for a variable, you can do this

p($str)->codegen_registered_methods(); // Output is var_export of the methods available
or
p($str)->puts_registered_methods() or p($str)->dump_registered_methods(); // echo and var_dump respectively

Diff type of vars will have diff methods available.

P($str) will have access to string functions like trim(), capitalize() but not get_class()
p($obj) will have access to object functions like get_class() but not trim()

Here is the lib.
http://karl.stephen.googlepages.com/prubyism.zip

You need to:

set_include_path('path/to/pruby');

function __autoload($class)
{
if (substr($class, 0, 5) == 'Pruby') Pruby::load_class($class);
}
Then,
require('Pruby.php');

I haven't actually created many modules yet. Just the string, array and object module.

Those are core modules. For optional addon modules, i created a demo Zend module.

For those who have the Zend framework, you can do this.

echo p('<p>hello</p>')->no_tags(); // Output: 'hello'

Because it is an addon. It is not loaded by default. You need to add this line after you include the Pruby.php file

Pruby::add_mods(array('Addon_Zend'));

So let me know your thoughts about it yeah? :)

Posted: Thu Nov 23, 2006 3:24 pm
by skruby
Oh yeah, for the sake of consistency,

all methods beginning with get_, is_, has_ will stop the chain.

so you can't

p($str)->trim()->is_empty()->capitalize();

because is_empty() will not return the Pruby object itself.

similarly,

p($arr)->pop()->merge(array(1, 2, 3))->get_key()->prepend(2321);

will not work as get_key will return the key and not the Pruby object. So prepend() will not work.

Posted: Thu Nov 23, 2006 8:34 pm
by Ambush Commander
So, essentially, you've created object wrappers for all the primitives. Sounds good (from my experience with JavaScript, I think primitives as objects is a very elegant idea) but expensive (performance-wise).

Posted: Thu Nov 23, 2006 8:49 pm
by John Cartwright
interesting :)

Posted: Thu Nov 23, 2006 11:48 pm
by skruby
Ambush Commander wrote:So, essentially, you've created object wrappers for all the primitives. Sounds good (from my experience with JavaScript, I think primitives as objects is a very elegant idea) but expensive (performance-wise).
Yes. Exactly. It's a wrapper to make those primitives like Int, Arrays etc become an object.

I tried to make it less expensive by

1) Using a static object wrapper. So each time you call p(), you are actually not creating a new Pruby object but reusing a globally available static Pruby object. So essentially, throughout the whole script, only 2 Pruby objects are created, one for p() and one for pp()

2) Lazy evaluation. I only instantiated the modules only if you are calling a function in that module. Eg. I've split all the PHP array functions to several modules. Mod_Array_Operator, Mod_Array_Iterator, Mod_Array_Functional etc.

Mod_Array_Functional has functional programming related functions like filter() or map()
Mod_Array_Iterator has iterating functions like next(), prev()

So if i do this: p($arr)->next()->map('trim')

I'll instantiate both modules.

But if i just do this: p($arr)->map('trim')

I won't instantiate both modules. Only the Mod_Array_Functional module is instantiated.

Posted: Fri Nov 24, 2006 12:21 am
by skruby
Jcart wrote:interesting :)

Hehe. Yeah. when i first had the idea, i was like 'Wow. wouldn't that be nice'?

I was having so much fun with jquery. Looking at my jquery code, it's so much more elegant compared to my PHP code.

Such a pity that PHP does not have first class functions. There's so many things you can do with first class functions.

Personally, I feel that PHP has gone the wrong way by adopting Java's object system. I very much prefer the Common Lisp Object System (CLOS) or Dylan's. Both approach allows for better reusability of code and less need for patterns. For eg. Dylan's generic functions and multi dispatch functions eliminate the need for the Visitor pattern.

Anyway, back to Pruby, i've created the entire framework to be a bunch of very loosely coupled classes. I hate it that frameworks like Cakephp/Symfony have classes that are so tightly coupled. They have so many nice helper functions like urls_to_links($post_with_urls) that will turn urls in posts to strings. But when i want to use just that function, i CAN'T! It's so tightly coupled to the entire framework.

So far, only Zend framework got it right. Every component is loosely coupled, following the PHP philosophy.

To prevent inheritance hell from deep tree structures due to extends A extends B ... extends Z, I purposely made every module a final class. Instead, I allow you to have multiple inheritance via the mixin method.

For eg.

Module A has function eat() and Module B has function walk()

You now want to create a module C that has the function eat_and_walk() like so

function eat_and_walk() { $this->eat(); $this->walk(); }

Instead of Module C extends Module B extends Module A, you do this.

class Module C {

public $requires = 'Module A, Module B';

function eat_and_walk() {...}
}

The Pruby Engine will handle the mixins and you now have access to functions in Module A and Module B.

Take a look at the module classes. It's trivial to create a module. I've purposely made each module not more than 300 lines to allow maximum reusability. So next time, if you want to mix and match the modules, you can easily do so.

Other possibilities: I'm creating a Object Attrs module that will eliminate setters and getters Ruby style.

So for eg.

class Dog {
public $name, $age;

public $attr_reader = 'name', $attr_writer = 'age';

}

$dog = new Dog();

When you want to set a value, you do this: p($dog)->age = 50;

This would work cos you have set the property age as writable.

But if you try p($dog)->name = 'doggie';

An exception will be thrown because the property name is readable only.

Oh man. So little time. So many ideas to implement. I'm still working on porting over the Rails helper functions like urls_to_links, emails_to_links to Pruby modules so you can do this.

echo p($post_with_email_and_urls)->linkify(); // Output: convert email and urls to links

or

echo p($post_with_email_and_urls)->linkify('emails'); // Output: convert only emails to links

and instead of

echo htmlspecialchars($text), do this: p($text)->esc_html();

and instead of
$str = implode('---', explode(',' $str)), do this: pp($str)->split(',')->join('---');

You see how much shorter and readable the above line of code is.

I also plan to create modules with a lot of oft-used shortcuts (pruby idioms as i call it).

For eg. p($arr)->set_default('key', 'default value') is one such short cut.

Another shortcut is this. pp($this->name)->set_if_not_null($name);

That is perfect for object constructor like so:

function __construct($name = NULL)
{
if (!is_null($name)) $this->name = $name;
}

I found myself littering my code with a lot of that. So pp($this->name)->set_if_not_null($name) is faster for the lazy me.

Posted: Fri Nov 24, 2006 8:49 am
by skruby
I'm having a design prob though.

Unlike Jquery where you can do $(a)->something($(b)->something())

My lib can't. p($a)->something(p($b)->something)) uses the same static object. Hence, $b will overwrite $a.

Sure. I can just create another object but is there any other solution that is more elegant. How does Jquery keep $a and $b separate. Do they create new objects for both?


Sideline: If anyone is interested in taking this project further, please let me know. I'm new to this open source thing and this project will be better off under the lead of someone far more experienced.

Posted: Fri Nov 24, 2006 9:02 am
by Mordred
Such a pity that PHP does not have first class functions.
It doesn't?

Code: Select all

create_function();
is_callable();
call_user_func();
call_user_func_array();
array_walk();
usort();

Posted: Fri Nov 24, 2006 9:10 am
by skruby
Mordred wrote:
Such a pity that PHP does not have first class functions.
It doesn't?

Code: Select all

create_function();
is_callable();
call_user_func();
call_user_func_array();
array_walk();
usort();

create_function is like a poor man's lambda. I prefer PHP to support treat functions and variables at the same level like javascript and the other functional programming languages.

Posted: Fri Nov 24, 2006 9:14 am
by feyd
Functions and variables are at the same level for the most part.

Posted: Fri Nov 24, 2006 10:06 am
by Mordred
skruby wrote:
skruby wrote:Such a pity that PHP does not have first class functions.
create_function is like a poor man's lambda.
Ah, but bad implementation is not the same as no implementation.

I personally avoid using create_function(), code generation is rarely feasible, and you usually want a constant lambda function anyway.
With code generation it is wise to make it cache-able, so create_function() is a no-no, eval() and save as .php is better. This fact does not invalidate the usefulness of the other callback-enabled functions.

What you are maybe against is the syntactic clumsiness attached to callbacks - it bugs me as well, but I'm ready to live with it, there are much more clumsy things in the syntax than this :)