Documents​  > ​

PHP as node.js Alternative Part 1: Hello World
Contents 1 Background 2 Problem 3 Goal 4 Analysis 4.1 Solution Candidates 4.2 Requirements 5 Implementations 5.1 node.js 5.2 Ratchet 5.3 Wrench 6 Conclusion

Background
node.js is the new cool kid in web programming world. It supports asynchronous, event­driven web server natively. It's very easy to communicate with clients (browser) through WebSockets (using Socket.IO) which makes implementing many real­time features possible. A little bit about WebSocket.

Problem
Is there any PHP alternative to accomplish equal task? Let's say for counting online user in real­time.

Goal
To implement real­time user count functionality using PHP as server­side. Use this stackoverflow answer as example of what we want to achieve.

Analysis
Solution Candidates
I started with a discussion in reddit and other discussions in stackoverflow and found these: React ­ Event­driven, non­blocking I/O with PHP. Pros used by Ratchet Cons documentation is lacking latest version 0.3.3 low level, for direct I/O, can't communicate with websocket directly, better use Ratchet for websocket Phastlight ­ asynchronous, event­driven command line tool and web server written in PHP 5.3+ inspired by Node.js Pros Cons latest version 0.1.x­dev "At this time, Phastlight is on its very early development phrases"

just like React, can't communicate to websocket directly Ratchet ­ PHP 5.3 library for asynchronously serving WebSockets. Pros documentation quite good Cons latest version 0.2.5 Wrench ­ Simple WebSocket Client/Server for PHP (formerly PHP WebSocket) Pros latest version 2.0.1 Cons documentation is lacking, there is an API doc but no user­friendly guide Use PHP persistent socket Pros no third­party library dependency Cons the article only explains the concept, we have to implement it manually and deal with any bug that might happen very hard to do 

Requirements
Provide a web page with text "There are: X connected users".  Every time a new window visits that page, increase X. Every time a window closes, decrease X. All browser windows must see the same number. Socket.IO is a node.js library that wraps the communication between client and server as if they are communicating through WebSocket. If the browser doesn't support WebSocket, it will fall back to more traditional approach like AJAX or Flash. Currently, there is no PHP equivalent for Socket.IO. Even if we can communicate with Socket.IO client (JavaScript browser) using the above WebSocket server libraries, if it falls back to AJAX for example, all the libraries above don't handle that. We need to handle that manually by imitating how Socket.IO communicate, or we could create our own Socket.IO replacement.  In general, because WebSocket is a new technology and only major browsers support it, it's a good idea to prepare a fallback method for old browsers. In node.js, we can easily accomplish this using Socket.IO.  In PHP, one possible solution would be to use Graceful WebSocket or similar library in client and implement two version of PHP servers (WebSocket only and HTTP only). Of course, then we will need to synchronize these two servers. In this document I will try to handle only communication through WebSocket between the client and server.

Implementations
All source code related to his document can be accessed in my github repository.

node.js
This is how we do it with node.js and Socket.IO: Server­side app.js v a ra p p=r e q u i r e ( ' h t t p ' ) . c r e a t e S e r v e r ( h a n d l e r ) ,i o=r e q u i r e ( ' s o c k e t . i o ' ) . l i s t e n ( a p p ) ,f s=r e q u i r e ( ' f s ' ) a p p . l i s t e n ( 8 0 8 8 ) ; f u n c t i o nh a n d l e r( r e q ,r e s ){ v a rf i l e p a t h=_ _ d i r n a m e+' / i n d e x . h t m l ' ; f s . r e a d F i l e ( f i l e p a t h ,f u n c t i o n( e r r ,d a t a ){ i f( e r r ){

}

r e s . w r i t e H e a d ( 5 0 0 ) ; r e t u r nr e s . e n d ( ' E r r o rl o a d i n gf i l e ' ) ;

}

r e s . w r i t e H e a d ( 2 0 0 ) ; r e s . e n d ( d a t a ) ; } ) ;

v a rc o u n t=0 ; f u n c t i o nu p d a t e _ c o u n t ( s o c k e t ){ s o c k e t . e m i t ( ' m e s s a g e ' ,{ c o u n t :c o u n t } ) ; s o c k e t . b r o a d c a s t . e m i t ( ' m e s s a g e ' ,{ c o u n t :c o u n t } ) ; } i o . s o c k e t s . o n ( ' c o n n e c t i o n ' ,f u n c t i o n( s o c k e t ){ c o u n t + + ; u p d a t e _ c o u n t ( s o c k e t ) ; s o c k e t . o n ( ' d i s c o n n e c t ' ,f u n c t i o n ( ){ c o u n t ; u p d a t e _ c o u n t ( s o c k e t ) ; } ) ; } ) ; Client­side index.html < h t m l > < h e a d > < t i t l e > N o d e J Sr e a lt i m eu s e rc o u n t < / t i t l e > < / h e a d > < b o d y >  < d i v > T h e r ea r e :  < s p a ni d = " c o u n t " > 0 < / s p a n >  c o n n e c t e du s e r s  < / d i v >  < s c r i p ts r c = " / s o c k e t . i o / s o c k e t . i o . j s " > < / s c r i p t >  < s c r i p t >   v a rd o m _ c o u n t=d o c u m e n t . g e t E l e m e n t B y I d ( ' c o u n t ' ) ;   v a rs o c k e t=i o . c o n n e c t ( ' h t t p : / / l o c a l h o s t : 8 0 8 8 ' ) ;   s o c k e t . o n ( ' m e s s a g e ' ,f u n c t i o n( d a t a ){   d o m _ c o u n t . i n n e r H T M L=d a t a . c o u n t ;   } ) ;  < / s c r i p t > < / b o d y > < / h t m l >

Ratchet
Main program src/MyApp/Count.php < ? p h p n a m e s p a c eM y A p p ; u s eR a t c h e t \ M e s s a g e C o m p o n e n t I n t e r f a c e ; u s eR a t c h e t \ C o n n e c t i o n I n t e r f a c e ; c l a s sC o u n ti m p l e m e n t sM e s s a g e C o m p o n e n t I n t e r f a c e{ p r o t e c t e d$ c l i e n t s ; p u b l i cf u n c t i o n_ _ c o n s t r u c t ( ){ $ t h i s > c l i e n t s=n e w\ S p l O b j e c t S t o r a g e ; } p u b l i cf u n c t i o no n O p e n ( C o n n e c t i o n I n t e r f a c e$ c o n n ){ / /S t o r et h en e wc o n n e c t i o nt os e n dm e s s a g e st ol a t e r $ t h i s > c l i e n t s > a t t a c h ( $ c o n n ) ; $ t h i s > b r o a d c a s t C o u n t ( ) ; e c h o" N e wc o n n e c t i o n !( { $ c o n n > r e s o u r c e I d } ) \ n " ; } p r o t e c t e df u n c t i o nb r o a d c a s t C o u n t ( ){ $ c o u n t=c o u n t ( $ t h i s > c l i e n t s ) ; f o r e a c h( $ t h i s > c l i e n t sa s$ c l i e n t ){

}

}

$ c l i e n t > s e n d ( $ c o u n t ) ;

p u b l i cf u n c t i o no n M e s s a g e ( C o n n e c t i o n I n t e r f a c e$ f r o m ,$ m s g ){ } p u b l i cf u n c t i o no n C l o s e ( C o n n e c t i o n I n t e r f a c e$ c o n n ){ / /T h ec o n n e c t i o ni sc l o s e d ,r e m o v ei t ,a sw ec a nn ol o n g e rs e n di t m e s s a g e s $ t h i s > c l i e n t s > d e t a c h ( $ c o n n ) ; $ t h i s > b r o a d c a s t C o u n t ( ) ; e c h o" C o n n e c t i o n{ $ c o n n > r e s o u r c e I d }h a sd i s c o n n e c t e d \ n " ; } p u b l i cf u n c t i o no n E r r o r ( C o n n e c t i o n I n t e r f a c e$ c o n n ,\ E x c e p t i o n$ e ){ e c h o" A ne r r o rh a so c c u r r e d :{ $ e > g e t M e s s a g e ( ) } \ n " ; } $ c o n n > c l o s e ( ) ;

}

The server bin/count­server.php < ? p h p u s eR a t c h e t \ S e r v e r \ I o S e r v e r ; u s eR a t c h e t \ W e b S o c k e t \ W s S e r v e r ; u s eM y A p p \ C o u n t ; r e q u i r ed i r n a m e ( _ _ D I R _ _ ).' / v e n d o r / a u t o l o a d . p h p ' ; $ s e r v e r=I o S e r v e r : : f a c t o r y (  n e wW s S e r v e r (  n e wC o u n t ( ) ,8 0 8 8 ) ; $ s e r v e r > r u n ( ) ; Client­side count.html < h t m l > < h e a d > < t i t l e > R a t c h e tr e a lt i m eu s e rc o u n t < / t i t l e > < / h e a d > < b o d y >  < d i v > T h e r ea r e :  < s p a ni d = " c o u n t " > 0 < / s p a n >  c o n n e c t e du s e r s  < / d i v >  < s c r i p t >   v a rd o m _ c o u n t=d o c u m e n t . g e t E l e m e n t B y I d ( ' c o u n t ' ) ;         v a rc o n n=n e wW e b S o c k e t ( ' w s : / / l o c a l h o s t : 8 0 8 8 ' ) ; c o n n . o n o p e n=f u n c t i o n ( e ){ } ;

 )

  c o n n . o n m e s s a g e=f u n c t i o n ( e ){   d o m _ c o u n t . i n n e r H T M L=e . d a t a ;   } ;  < / s c r i p t > < / b o d y > < / h t m l > Note that we don't handle any fallback, if the browser doesn't support WebSocket then this code won't work.

Wrench
Main program src/MyApp/CountApplication.php < ? p h p

n a m e s p a c eM y A p p ; u s eW r e n c h \ A p p l i c a t i o n \ A p p l i c a t i o n ; / * * *E x a m p l ea p p l i c a t i o nf o rW r e n c h :d i s p l a yu s e rc o u n t * / c l a s sC o u n t A p p l i c a t i o ne x t e n d sA p p l i c a t i o n { p r o t e c t e d$ c l i e n t s=a r r a y ( ) ; / * * *@ s e eW r e n c h \ A p p l i c a t i o n . A p p l i c a t i o n : : o n C o n n e c t ( ) * / p u b l i cf u n c t i o no n C o n n e c t ( $ c l i e n t ) { $ i d=$ c l i e n t > g e t I d ( ) ; / /e c h o" c o n n e c t e d :$ i d \ n " ; $ t h i s > c l i e n t s [ $ i d ]=$ c l i e n t ; $ t h i s > b r o a d c a s t C o u n t ( ) ; } p u b l i cf u n c t i o no n D i s c o n n e c t ( $ c l i e n t ) {  $ i d=$ c l i e n t > g e t I d ( ) ; / /e c h o" d i s c o n n e c t e d :$ i d \ n " ;  u n s e t ( $ t h i s > c l i e n t s [ $ i d ] ) ; $ t h i s > b r o a d c a s t C o u n t ( ) ; } p r o t e c t e df u n c t i o nb r o a d c a s t C o u n t ( ) {  $ c o u n t=c o u n t ( $ t h i s > c l i e n t s ) ;  f o r e a c h( $ t h i s > c l i e n t sa s$ c l i e n t ){  $ c l i e n t > s e n d ( $ c o u n t ) ;  } } p u b l i cf u n c t i o no n D a t a ( $ d a t a ,$ c l i e n t ) { / /n o tu s e d }

}

The server src/server.php < ? p h p r e q u i r ed i r n a m e ( _ _ D I R _ _ ).' / v e n d o r / a u t o l o a d . p h p ' ; u s eW r e n c h \ B a s i c S e r v e r ; $ s e r v e r=n e wB a s i c S e r v e r ( ' w s : / / l o c a l h o s t : 8 0 8 8 ' ,a r r a y ( ' c h e c k _ o r i g i n '= >f a l s e ) ) ; / /R e g i s t e ry o u ra p p l i c a t i o n s $ s e r v e r > r e g i s t e r A p p l i c a t i o n ( ' c o u n t ' ,n e wM y A p p \ C o u n t A p p l i c a t i o n ( ) ) ; $ s e r v e r > r u n ( ) ; Client­side index.html < h t m l > < h e a d > < t i t l e > W r e n c hr e a lt i m eu s e rc o u n t < / t i t l e > < / h e a d > < b o d y >  < d i v > T h e r ea r e :  < s p a ni d = " c o u n t " > 0 < / s p a n >  c o n n e c t e du s e r s  < / d i v >  < s c r i p t >   v a rd o m _ c o u n t=d o c u m e n t . g e t E l e m e n t B y I d ( ' c o u n t ' ) ;         v a rc o n n=n e wW e b S o c k e t ( ' w s : / / l o c a l h o s t : 8 0 8 8 / c o u n t ' ) ; c o n n . o n o p e n=f u n c t i o n ( e ){ } ;

  c o n n . o n m e s s a g e=f u n c t i o n ( e ){   d o m _ c o u n t . i n n e r H T M L=e . d a t a ;   } ;  < / s c r i p t > < / b o d y > < / h t m l > Just like Ratchet, this implementation doesn't handle any fallback.

Conclusion
Among those candidates, only Ratchet and Wrench that could accomplish the task. Ratchet has better documentation while Wrench has better development. Even with that, they still couldn't replace node.js+Socket.IO completely because they don't have the fallback feature. So, keep that in mind if you insist on using PHP.

Sign up to vote on this title
UsefulNot useful