33namespace React ;
44
55abstract class Component{
6- private static $ isTagsSet = false ;
7- private static $ htmlTags = ['div ' ,'p ' ,'img ' ,'h1 ' ,'h2 ' ,'h3 ' ,'h4 ' ,'h5 ' ,'h6 ' ,'iframe ' ,'article ' , 'form ' ,'input ' ,'textarea ' ,'select ' ,'option ' , 'link ' , 'script ' , 'button ' , 'nav ' , 'title ' , 'meta ' ];
8- private static $ hasNoChild = ['img ' , 'link ' , 'input ' , 'meta ' ];
9- private const tagNameSpace = 'React\Tag ' ;
10- private static $ counter = 1 ;
11- protected $ id = '' ;
12- protected $ state = [];
13- private static $ states = [];
6+ private static $ isTagsSet = false ; //flag to track if all html tag classes created
147
8+ //all html tags that are allowed
9+ private static $ htmlTags = ['div ' ,'p ' ,'img ' ,'a ' ,'ul ' ,'li ' , 'h1 ' ,'h2 ' ,'h3 ' ,'h4 ' ,'h5 ' ,'h6 ' ,'iframe ' ,'article ' , 'form ' ,'input ' ,'textarea ' ,'select ' ,'option ' , 'link ' , 'script ' , 'button ' , 'nav ' , 'title ' , 'meta ' , 'code ' , 'pre ' , 'abbr ' ];
10+
11+ private static $ hasNoChild = ['img ' , 'link ' , 'input ' , 'meta ' ]; //tags that have no children
12+ private const tagNameSpace = 'React\Tag ' ; //name space for the tags
13+
14+ private static $ counter = 1 ; // couter for generating sequencial id
15+ protected $ id = '' ; //the current id of the component
16+ protected $ state = []; //the current state
17+ private static $ states = []; //used to save all states of every component in the page
18+
19+ /*
20+ run the first time when first component called
21+ responsible for:
22+ - setting all tags class component
23+ - setting the script that controls the state
24+ */
1525 private function setTags (){
1626 @ob_start ();
1727 foreach (self ::$ htmlTags as $ el ){
@@ -23,47 +33,97 @@ private function setTags(){
2333 echo new \React \Tag \script ('const phpReact={setState:function(t,e,n){var a=document.getElementById(t);if(a){var r=this.getState(t);"function"==typeof e&&(e=e(r));var o=new XMLHttpRequest;o.onreadystatechange=function(){4==this.readyState&&200==this.status&&(a.outerHTML=this.responseText,"function"==typeof n&&n())},o.open("POST",location.href,!0),o.setRequestHeader("Content-type","application/x-www-form-urlencoded"),o.send("phpreact="+JSON.stringify({id:t,state:e,prevState:r}))}},getState:function(t){try{var e=document.getElementById(t);return JSON.parse(e.getAttribute("prevstate"))}catch(t){return{}}}}; ' );
2434 }
2535
26- private function getTagName (){
36+ /*
37+ @return the current component tag name
38+ */
39+ protected function getTagName (){
2740 return trim (str_replace (self ::tagNameSpace, '' , get_class ($ this )), '\\' );
2841 }
29- private function isHtmlTage (){
42+
43+ /*
44+ @return check if the current component is html tag
45+ */
46+ protected function isHtmlTage (){
3047 return in_array ($ this ->getTagName (), self ::$ htmlTags );
3148 }
32- private function hasNoChild (){
49+
50+ /*
51+ @return check if the current component is has no children
52+ */
53+ protected function hasNoChild (){
3354 return in_array ($ this ->getTagName (), self ::$ hasNoChild );
3455 }
3556
57+ /*
58+ register custom html tag
59+ @param: $tag: string|array[list of string] html tags
60+ @param: $hasNoChild: bool if the tags accept no children
61+ */
3662 static function registerTag ($ tags , $ hasNoChild = false ){
3763 self ::$ htmlTags = array_unique (array_merge (self ::$ htmlTags , (array )$ tags ));
3864 if ($ hasNoChild ) $ this ->setHasNoChild ($ tags );
3965 }
4066
67+ /*
68+ set the custom tags that has no children
69+ @param: $tag: string|array[list of string] html tags
70+ */
4171 static function setHasNoChild ($ tags ){
4272 self ::$ hasNoChild = array_unique (array_merge (self ::$ hasNoChild , (array )$ tags ));
4373 }
4474
75+ /*
76+ render the html tag only
77+ */
4578 function render (){
4679 if (!$ this ->isHtmlTage ()) return '' ;
4780
48- //save states in dom attribute [prevState ]
81+ //save states in dom attribute [prevstate ]
4982 if ($ this ->props ->id && self ::$ states [$ this ->props ->id ])
50- $ this ->props ->prevState = json_encode (self ::$ states [$ this ->props ->id ]);
83+ $ this ->props ->prevstate = json_encode (self ::$ states [$ this ->props ->id ]);
5184
5285 $ tag = $ this ->getTagName ();
86+ $ innerHtml = '' ;
5387 $ attr = [];
54- foreach ($ this ->props as $ k => $ v ){ $ attr [] = $ k .'=" ' .htmlspecialchars ($ v ).'" ' ; }
88+ foreach ($ this ->props as $ k => $ v ){
89+ if ($ k == 'dangerouslyInnerHTML ' ){ //if has dangerouslyInnerHTML attribute
90+ $ innerHtml = $ v ; continue ;
91+ }
92+ $ att = preg_replace ('/[^\w-]/ ' ,'' , $ k ); //allow only [words or dash]
93+ $ val = htmlspecialchars ($ v ); //escape html
94+
95+ $ attr [] = "$ att=' $ v' " ;
96+ }
5597 $ attributes = implode (' ' ,$ attr );
56- $ children = implode ('' , $ this ->children );
98+
99+ //if theres innerHtml then ignore children else escape any string passed as html
100+ $ children = $ innerHtml ? [$ innerHtml ] : array_map (function ($ v ){ return is_string ($ v ) ? htmlspecialchars ($ v ) : $ v ; }, $ this ->children );
101+ $ children = implode ('' , $ children );
57102
58103 return "< $ tag $ attributes> $ children</ $ tag> " ;
59104 }
60105
106+ /*
107+ parse the components to html
108+ @return: html string
109+ */
61110 function __toString (){
62111 $ components = $ this ->render ();
63- if (!is_array ($ components )) $ components = [$ components ];
112+ if (!is_array ($ components )) $ components = [$ components ]; //must be list of components
113+
114+ //if custom component the render should return component or list of components
115+ if (!$ this ->isHtmlTage ()) $ components = array_filter ($ components , function ($ v ){ return $ v instanceof Component; });
116+
64117 return implode ('' , $ components );
65118 }
66119
120+ /*
121+ construct the tag with list of child component and props
122+ @param: $children: component|array[of component]
123+ @param: $props: associative array of key=> value
124+
125+ @usage: Component($children, $props) or Component($props) if exists in hasNoChild
126+ */
67127 function __construct ($ children = [], $ props = []){
68128 if (!self ::$ isTagsSet ) $ this ->setTags ();
69129 $ hasNoChild = $ this ->hasNoChild ();
@@ -80,15 +140,28 @@ function __construct($children = [], $props = []){
80140 $ this ->setStateListener ();
81141 }
82142
143+ /*
144+ run only when state has been updated
145+ @param: $oldState: [object] the previous state
146+ @param: $currentState: [object] the current state
147+ */
83148 function componentDidUpdate ($ oldState , $ currentState ){}
84149
150+ /*
151+ set unique id of each custom component
152+ it's used to check against when state updated
153+ */
85154 private function setId (){
86155 if ($ this ->isHtmlTage ()) return ;
87156 $ this ->id = md5 (self ::$ counter ); //generate id
88157 self ::$ states [$ this ->id ] = $ this ->state ; //save all states by id
89158 self ::$ counter ++;
90159 }
91160
161+ /*
162+ ajax listner of state update
163+ ajax posts 'phpreact' with json that has attributes [id: component id, state: the new state, prevState: the current state];
164+ */
92165 private function setStateListener (){
93166 if (empty ($ _POST ['phpreact ' ])) return ;
94167 $ post = json_decode ($ _POST ['phpreact ' ]);
0 commit comments