nexus-scholar/graph-core
Lightweight PHP 8.2+ graph data-structure for directed/undirected graphs with node/edge attributes, fast adjacency via integer indexing, read-only subgraph views, and exporters for Cytoscape.js JSON, GraphML, and GEXF. Ideal base for graph analytics.
Installation:
composer require nexus-scholar/graph-core
Ensure PHP 8.2+ and ext-dom are enabled for XML exports.
First Use Case: Model a simple directed graph (e.g., citation network):
use nexus-scholar\graph_core\Domain\Graph;
$graph = new Graph(directed: true);
$graph->addNode('paper1', ['title' => 'Research Paper 1']);
$graph->addEdge('paper1', 'paper2', ['cites' => true]);
Where to Look First:
Graph (mutable), SubgraphView (read-only), Node, Edge.CytoscapeJsonExporter, GraphMLExporter, GexfExporter (for visualization).Domain/ for graph operations and IO/ for exports.Graph Construction:
Graph::fromEdgeList() for bulk imports (e.g., from CSV/JSON):
$edges = [['paper1', 'paper2', ['year' => 2020]]];
$graph = Graph::fromEdgeList($edges, directed: true);
addNode() before addEdge() to avoid errors.Querying:
successors()/predecessors() for traversal:
$citations = $graph->successors('paper1', ['cites' => true]);
$subgraph = new SubgraphView($graph, ['paper1', 'paper2']);
Attributes:
['weight' => 1.0, 'label' => '...']).nodeAttrs()/edgeAttrs():
$title = $graph->nodeAttrs('paper1')['title'];
Exports:
$exporter = new \nexus-scholar\graph_core\IO\CytoscapeJsonExporter();
$json = $exporter->export($graph);
$gexf = (new \nexus-scholar\graph_core\IO\GexfExporter())->export($graph);
Data Pipeline:
[CSV/JSON] → Graph::fromEdgeList() → SubgraphView → Export → Visualization
Example: Filter a citation graph by year before exporting:
$recentPapers = array_filter($graph->nodes(), fn($id) =>
$graph->nodeAttrs($id)['year'] > 2020
);
$subgraph = new SubgraphView($graph, array_keys($recentPapers));
Integration with Algorithms:
nexus-scholar/graph-algorithms for pathfinding/centrality:
$centrality = (new \nexus_scholar\graph_algorithms\Centrality\DegreeCentrality())
->calculate($graph);
Testing:
SubgraphView to isolate test cases:
$testGraph = new SubgraphView($graph, ['paper1', 'paper2']);
$this->assertTrue($testGraph->hasEdge('paper1', 'paper2'));
$paper->graph_data = json_encode($graph->toArray());
GraphExportJob::dispatch($graph, 'gexf')->onQueue('exports');
SubgraphView instances for repeated queries:
$cache->remember("subgraph_{$key}", 3600, fn() =>
new SubgraphView($graph, $nodeIds)
);
Node/Edge Existence:
hasEdge()/hasNode() return false for undirected graphs if the edge isn’t explicitly added (even if logically bidirectional).$graph->addEdge('A', 'B'); // Undirected: adds A→B and B→A
Attribute Overwrites:
addEdge()/addNode() overwrite existing attributes if the same key is reused.updateEdgeAttrs()/updateNodeAttrs() for partial updates:
$graph->updateEdgeAttrs('A', 'B', ['weight' => 2.0]);
Subgraph Views:
$mutableCopy = clone $graph;
Exporter Dependencies:
GraphMLExporter/GexfExporter require ext-dom. Use CytoscapeJsonExporter as a fallback:
if (!extension_loaded('dom')) {
$exporter = new \nexus-scholar\graph_core\IO\CytoscapeJsonExporter();
}
Performance:
p1, p2) for large graphs.Graph State:
dd([
'nodes' => $graph->nodes(),
'edges' => $graph->edges(),
'attrs' => $graph->nodeAttrs('paper1'),
]);
Edge Directionality:
$graph = new Graph(directed: false);
$graph->addEdge('A', 'B');
var_dump($graph->hasEdge('B', 'A')); // true (undirected)
Exporter Issues:
json_validate():
if (json_validate($json) === JSON_ERROR_NONE) {
// Valid JSON
}
Type Safety:
class CitationGraph extends Graph {
public function __construct(public array $citationRules = []) {}
}
Serialization:
JsonSerializable for easy storage:
class GraphJsonSerializer implements \JsonSerializable {
public function jsonSerialize(): array {
return [
'nodes' => $this->graph->nodes(),
'edges' => $this->graph->edges(),
];
}
}
Extension Points:
ExporterInterface for new formats:
class DotExporter implements ExporterInterface {
public function export(GraphInterface $graph): string {
// Custom DOT format logic
}
}
Graph with domain-specific traits:
trait CitationGraphTrait {
public function addCitation(string $from, string $to, int $year): void {
$this->addEdge($from, $to, ['year' => $year, 'type' => 'citation']);
}
}
Configuration:
// config/graph.php
return [
'default_directed' => env('GRAPH_DIRECTED', true),
];
$graph = new Graph(directed: config('graph.default_directed'));
Large Graphs:
SubgraphView to limit memory usage during analysis:
$view = new SubgraphView($graph, array_slice($nodeIds, 0, 1000));
$exporter->export($subgraph)->writeToFile("part_{$i}.gexf");
How can I help you explore Laravel packages today?