Skip to content

Commit cb5c0ed

Browse files
Update React.php
1 parent b288e58 commit cb5c0ed

File tree

1 file changed

+89
-16
lines changed

1 file changed

+89
-16
lines changed

React.php

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,25 @@
33
namespace React;
44

55
abstract 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

Comments
 (0)