339 lines
7.1 KiB
PHP
339 lines
7.1 KiB
PHP
<?php
|
|
|
|
class Graph
|
|
{
|
|
|
|
protected $initialList;
|
|
protected $open;
|
|
protected $closed;
|
|
public $nodes;
|
|
public $searchNodes;
|
|
|
|
/**
|
|
* @throws ErrorException
|
|
*/
|
|
public function __construct(
|
|
$__initialList,
|
|
$__build = true
|
|
)
|
|
{
|
|
$this->initialList = $__initialList;
|
|
$this->open = null;
|
|
$this->closed = null;
|
|
$this->nodes = [];
|
|
$this->searchNodes = [];
|
|
|
|
/**
|
|
* Step 1. Create a Search graph
|
|
*/
|
|
if ($__build) {
|
|
$this->build();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return Node
|
|
* @throws Exception
|
|
*/
|
|
protected function popFirst(&$__list)
|
|
{
|
|
/**
|
|
* Step 3. If OPEN is empty - throw error (we are actually done)
|
|
*/
|
|
if (sizeof($__list) === 0) {
|
|
throw new Exception("Done");
|
|
}
|
|
|
|
return array_splice($__list, 0, 1)[0];
|
|
}
|
|
|
|
/**
|
|
* @throws ErrorException
|
|
*/
|
|
protected function remove($__name, &$__list)
|
|
{
|
|
for ($i = 0; $i < sizeof($__list); $i++) {
|
|
if ($__list[$i]->name === $__name) {
|
|
return array_splice($__list, $i, 1)[0];
|
|
}
|
|
}
|
|
throw new ErrorException('Node not found in list : ' . $__name);
|
|
}
|
|
|
|
protected function removeAll($__parent, &$__list)
|
|
{
|
|
|
|
$nodes = [];
|
|
|
|
for ($i = sizeof($__list) - 1; $i >= 0; $i--) {
|
|
if ($__list[$i]->parent === $__parent) {
|
|
array_push($nodes, array_splice($__list, $i, 1)[0]);
|
|
}
|
|
}
|
|
|
|
return $nodes;
|
|
}
|
|
|
|
/**
|
|
* Step 6. Expand $node by generating a set M of successors that are not already ancestors of
|
|
* $node in G. Install them into G.
|
|
*/
|
|
protected function expand($node)
|
|
{
|
|
if ($node->name === 'R3') {
|
|
$children = $this->removeAll(null, $this->initialList);
|
|
} else {
|
|
$children = $this->removeAll($node->name, $this->initialList);
|
|
}
|
|
return $children;
|
|
}
|
|
|
|
protected function linkToParent($node)
|
|
{
|
|
foreach ($node->children as $child) {
|
|
$child->nameSpace = $node->nameSpace . $node->nameSpaceClassName . '.';
|
|
$child->parent = $node;
|
|
}
|
|
}
|
|
|
|
protected function pushToList(&$__list, $__nodes)
|
|
{
|
|
foreach ($__nodes as $node) {
|
|
array_push($__list, $node);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
protected function processOpen($searchMode = false, $property = null, $value = null, $regex = false)
|
|
{
|
|
/**
|
|
* Step 3. If OPEN is empty - throw Exception (we are actually done)
|
|
*/
|
|
$node = $this->popFirst($this->open);
|
|
|
|
/**
|
|
* Step 4. Select the first node on OPEN - move it to CLOSED
|
|
*/
|
|
array_push($this->closed, $node);
|
|
|
|
// array_push($this->searchNodes, $node);
|
|
|
|
/**
|
|
* Step 5. If $node is a GOAL node - we are done - we skip this step for build because then we are done
|
|
* only when $this->open is empty
|
|
*/
|
|
if ($searchMode) {
|
|
|
|
if ($property === null) {
|
|
throw new ErrorException('Search mode cannot be true without a property specified');
|
|
}
|
|
|
|
if ($regex) {
|
|
|
|
if (preg_match($value, $node->$property)) {
|
|
return $node;
|
|
}
|
|
|
|
} else {
|
|
|
|
if ($node->$property === $value) {
|
|
return $node;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Step 6. Expand $node by generating a set M of successors that are not already ancestors of
|
|
* $node in G. Install them into G.
|
|
*
|
|
* We skip 6. and 7. a) when in search mode, because we already have children linked to parents
|
|
*/
|
|
if (!$searchMode) {
|
|
|
|
$successors = $this->expand($node);
|
|
|
|
$node->children = $successors;
|
|
|
|
/**
|
|
* Step 7. a) Establish a link from each successor to its parent
|
|
*/
|
|
$this->linkToParent($node);
|
|
}
|
|
|
|
/**
|
|
* Step 7. b) Add all successors to OPEN
|
|
*/
|
|
$this->pushToList($this->open, $node->children);
|
|
|
|
/**
|
|
* Step 8. We skip this because we do not do any re-ordering on the graph
|
|
*/
|
|
|
|
/**
|
|
* Step 9. Go back to Step 3
|
|
*/
|
|
return $this->processOpen($searchMode, $property, $value, $regex);
|
|
|
|
}
|
|
|
|
/**
|
|
* @param $property
|
|
* @param $value
|
|
* @param $regex
|
|
* @return Array | Node
|
|
* @throws ErrorException
|
|
*/
|
|
public function search($property, $value, $regex = false)
|
|
{
|
|
if (sizeof($this->nodes) <= 0) {
|
|
throw new ErrorException('This graph is not built yet');
|
|
}
|
|
|
|
/**
|
|
* Step 1. Create a Search graph containing only the start node - put it on a list called OPEN
|
|
*/
|
|
$startNode = $this->nodes[0];
|
|
|
|
$this->searchNodes = [];
|
|
|
|
array_push($this->searchNodes, $startNode);
|
|
|
|
$this->open = [];
|
|
|
|
array_push($this->open, $startNode);
|
|
|
|
/**
|
|
* Step 2. Create a list called CLOSED initially empty
|
|
*/
|
|
$this->closed = [];
|
|
|
|
try {
|
|
/**
|
|
* Step 3. If OPEN is empty - we could not find the node
|
|
*/
|
|
return $this->processOpen(true, $property, $value, $regex);
|
|
} catch (Exception $e) {
|
|
return $this->searchNodes;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws ErrorException
|
|
*/
|
|
protected function build()
|
|
{
|
|
|
|
if (sizeof($this->initialList) <= 0) {
|
|
throw new ErrorException('Bad initial list');
|
|
}
|
|
/**
|
|
* Step 1. Create a Search graph containing only the start node - put it on a list called OPEN
|
|
*/
|
|
$startNode = $this->remove("R3", $this->initialList);
|
|
|
|
array_push($this->nodes, $startNode);
|
|
|
|
$this->open = [];
|
|
|
|
array_push($this->open, $startNode);
|
|
|
|
/**
|
|
* Step 2. Create a list called CLOSED initially empty
|
|
*/
|
|
$this->closed = [];
|
|
|
|
try {
|
|
/**
|
|
* Step 3. If OPEN is empty - throw Exception (we are actually done)
|
|
*/
|
|
return $this->processOpen();
|
|
} catch (Exception $e) {
|
|
return $e->getMessage();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns all nodes in the graph
|
|
* @return []
|
|
*/
|
|
public function walk()
|
|
{
|
|
try {
|
|
$this->search('name', 'all');
|
|
return $this->closed;
|
|
} catch (ErrorException $e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns all children of this node in a flat (1 dimensional) array
|
|
* @param $node
|
|
*/
|
|
public function flatten($node)
|
|
{
|
|
|
|
if (sizeof($node->children) === 0) {
|
|
return [];
|
|
}
|
|
|
|
$result = [];
|
|
|
|
foreach ($node->children as $child) {
|
|
|
|
array_push($result, $child);
|
|
|
|
$children = $this->flatten($child);
|
|
|
|
foreach ($children as $child2) {
|
|
array_push($result, $child2);
|
|
}
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
|
}
|
|
|
|
class Node
|
|
{
|
|
public $file = '';
|
|
public $name = '';
|
|
public $nameSpace = '';
|
|
public $parent = null;
|
|
public $children = [];
|
|
public $isBaseClass = null;
|
|
public $serverSide = false;
|
|
public $clientSide = false;
|
|
public $default = false;
|
|
|
|
function __construct(
|
|
$__file,
|
|
$__name,
|
|
$__nameSpace = '',
|
|
$__nameSpaceClassName = '',
|
|
$__parent = null,
|
|
$__children = [],
|
|
$__isBaseClass = null,
|
|
$__serverSide = false,
|
|
$__clientSide = false,
|
|
$__default = false
|
|
)
|
|
{
|
|
$this->file = $__file;
|
|
$this->name = $__name;
|
|
$this->nameSpace = $__nameSpace;
|
|
$this->nameSpaceClassName = $__nameSpaceClassName;
|
|
$this->parent = $__parent;
|
|
$this->children = $__children;
|
|
$this->isBaseClass = $__isBaseClass;
|
|
$this->serverSide = $__serverSide;
|
|
$this->clientSide = $__clientSide;
|
|
$this->default = $__default;
|
|
}
|
|
|
|
} |