I was inspired by the Wikipedia pages Topological sorting . So I made some own algorithm.
class Top
{
protected $_nodes = array();
protected $_structure = array();
protected $_sortedNodes;
protected $_level = 0;
protected $_singleNonEdgeNode = true;
public function isModeSingleNonEdgeNode()
{
return $this->_singleNonEdgeNode;
}
public function enableModeSingleNonEdgeNode($flag)
{
$this->_singleNonEdgeNode = (bool)$flag;
return $this;
}
public function addNode($name, array $dependsOn = array())
{
if (null !== $this->_sortedNodes) {
throw new Exception('Nodes already sorted.');
}
$this->_nodes[$name] = $name;
$this->_structure[$name] = $dependsOn;
return $this;
}
public function getSortedNodes()
{
if (null === $this->_sortedNodes) {
$this->_sortedNodes = array();
$this->_performNonEdgedNodes();
$this->_performEdgedNodes();
}
return $this->_sortedNodes;
}
protected function _moveNodeToSortedList($name)
{
$node = $this->_takeNode($name);
if ($node) {
$this->_sortedNodes[] = $node;
} else {
throw new Exception("The node '$name' has already been taken.");
}
return $this;
}
protected function _takeNode($name)
{
if (!isset($this->_nodes[$name])) {
return null;
}
$node = $this->_nodes[$name];
unset($this->_nodes[$name]);
return $node;
}
protected function _performNode($name)
{
$node = $this->_takeNode($name);
if (null === $node) {
return $this;
}
foreach ($this->_structure[$node] as $depNode) {
$this->_checkCycledEdges($node, $depNode);
$this->_performNode($depNode);
}
$this->_sortedNodes[] = $node;
return $this;
}
protected function _checkCycledEdges($node, $edgeNode)
{
if (in_array($node, $this->_structure[$edgeNode])) {
throw new Exception("Cyclic dependencies between $edgeNode and $node.");
}
return true;
}
protected function _performEdgedNodes()
{
while (!empty($this->_nodes)) {
$name = current($this->_nodes);
$this->_performNode($name);
}
return $this;
}
protected function _performNonEdgedNodes()
{
foreach ($this->_structure as $name => $edges) {
if (!$edges) {
$this->_moveNodeToSortedList($name);
if ($this->isModeSingleNonEdgeNode()) {
break;
}
}
}
return $this;
}
}
And did tests for this class:
<?php
namespace Lib\Sort;
class TopTest extends \PHPUnit_Framework_TestCase
{
public function testBasicSorting()
{
$test = new Top();
$test->addNode('A');
$test->addNode('B', array('A', 'F'));
$test->addNode('C', array('B', 'D'));
$test->addNode('D', array('F'));
$test->addNode('E', array('A', 'F'));
$test->addNode('F', array('A'));
$test->addNode('G');
$this->assertTrue($test->isModeSingleNonEdgeNode());
$actual = $test->getSortedNodes();
$expected = array('A', 'F', 'B', 'D', 'C', 'E', 'G');
$this->assertEquals($expected, $actual);
}
public function testLastNodeSorting()
{
$test = new Top();
$test->addNode('A', array());
$test->addNode('B', array('A', 'F'));
$test->addNode('C', array('B', 'D'));
$test->addNode('D', array('F'));
$test->addNode('E', array('A'));
$test->addNode('F', array('A', 'E'));
$actual = $test->getSortedNodes();
$expected = array('A', 'E', 'F', 'B', 'D', 'C');
$this->assertEquals($actual, $expected);
}
public function testDisabledSingleNonEdgesSorting()
{
$test = new Top();
$test->addNode('A');
$test->addNode('B', array('A', 'F'));
$test->addNode('C', array('B', 'D'));
$test->addNode('D', array('F'));
$test->addNode('E', array('A'));
$test->addNode('F', array('A', 'E'));
$test->addNode('G');
$test->enableModeSingleNonEdgeNode(false);
$actual = $test->getSortedNodes();
$expected = array('A', 'G', 'E', 'F', 'B', 'D', 'C');
$this->assertEquals($actual, $expected);
}
public function testCyclicSortingFailure()
{
$test = new Top();
$test->addNode('A', array());
$test->addNode('B', array('A', 'F'));
$test->addNode('C', array('B', 'D'));
$test->addNode('D', array('F'));
$test->addNode('E', array('A', 'F'));
$test->addNode('F', array('A', 'E'));
$test->getSortedNodes();
}
public function testAddNodeAfterSortingFailure()
{
$test = new Top();
$test->addNode('A', array());
$test->addNode('B', array('A', 'F'));
$test->addNode('C', array('B', 'D'));
$test->addNode('D', array('F'));
$test->addNode('E', array('A'));
$test->addNode('F', array('A', 'E'));
$test->getSortedNodes();
$test->addNode('H', array('E'));
}
}
Maybe it will be useful for someone.