PHPackages                             cmuset/chess-tools - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Validation &amp; Sanitization](/categories/validation)
4. /
5. cmuset/chess-tools

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

cmuset/chess-tools
==================

A chess tools library for parsing and exporting chess notations (PGN, SAN, FEN).

v2.0.2(2mo ago)13MITPHPPHP &gt;=8.4CI passing

Since Oct 12Pushed 2mo ago1 watchersCompare

[ Source](https://github.com/clemuset/chess-tools)[ Packagist](https://packagist.org/packages/cmuset/chess-tools)[ RSS](/packages/cmuset-chess-tools/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (3)Dependencies (6)Versions (11)Used By (0)

ChessTools
==========

[](#chesstools)

A PHP library for parsing and exporting chess notations: **PGN**, **SAN**, and **FEN**.

Table of Contents
-----------------

[](#table-of-contents)

1. [Installation](#installation)
2. [Quick Start](#quick-start)
3. [Tools](#tools)
4. [Model Layer](#model-layer)
5. [Enums Reference](#enums-reference)
6. [Testing &amp; Development](#testing--development)

---

Installation
------------

[](#installation)

Requires PHP &gt;= 8.4.

```
composer require cmuset/chess-tools
```

---

Quick Start
-----------

[](#quick-start)

```
use Cmuset\ChessTools\Model\Game;

// Parse a PGN string
$game = Game::fromPGN($pgn);

echo $game->getTag('White');     // 'Kasparov, Garry'
echo $game->getResult()->value;  // '1-0'

// Iterate moves
foreach ($game->getMainLine() as $key => $node) {
    echo $key . ' ' . $node->getMove()->getSAN(); // '1. e4', '1... e5', ...

    foreach ($node->getVariations() as $variation) {
        // Alternative lines branching from this move
    }
}

// Apply moves to a position
use Cmuset\ChessTools\Model\Position;
use Cmuset\ChessTools\Tool\Parser\PGNParser;

$pos = Position::fromFEN(PGNParser::INITIAL_FEN);
$pos->applyMove('e4');
$pos->applyMove('e5');
echo $pos->getFEN(); // 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1'

// Export
echo $game->getPGN();      // Full PGN with tags, comments, variations
echo $game->getLitePGN();  // Moves only
```

---

Tools
-----

[](#tools)

### Parsers — [`docs/tools/parser.md`](docs/tools/parser.md)

[](#parsers--docstoolsparsermd)

Convert strings into model objects.

ClassInputOutput`PGNParser`PGN string`Game` or `Game[]``SANParser`SAN string + color`Move``FENParser`FEN string`Position````
use Cmuset\ChessTools\Tool\Parser\PGNParser;

$parser = PGNParser::create();
$game   = $parser->parse($pgn);         // Game | Game[]
```

### Exporters — [`docs/tools/exporter.md`](docs/tools/exporter.md)

[](#exporters--docstoolsexportermd)

Serialize model objects back to strings.

ClassInputOutput`GameExporter``Game` or `Variation`PGN string`MoveExporter``Move`SAN string`PositionExporter``Position`FEN string```
$game->getPGN();        // full PGN
$game->getLitePGN();    // moves only
$game->getVerbosePgn(); // with resolved source squares and check/mate markers
$position->getFEN();
$move->getSAN();
```

### MoveApplier — [`docs/tools/move-applier.md`](docs/tools/move-applier.md)

[](#moveapplier--docstoolsmove-appliermd)

Applies a `Move` to a `Position`, enforcing all chess rules: castling rights, en passant, promotion, counters, and post-move check validation.

```
use Cmuset\ChessTools\Tool\MoveApplier\Exception\MoveApplyingException;

try {
    $pos->applyMove('Nf3');
} catch (MoveApplyingException $e) {
    echo $e->getMoveViolation()->value; // e.g. 'No piece found for the move'
}

$pos->getLegalMoves(); // Move[] — all legal moves for the side to move
```

### Validators — [`docs/tools/validator.md`](docs/tools/validator.md)

[](#validators--docstoolsvalidatormd)

Check positions and games for illegal states.

ClassValidates`PositionValidator`A single `Position` (kings, check, pawns, en passant)`GameValidator`A full `Game` — replays every move including sub-variations```
use Cmuset\ChessTools\Tool\Validator\PositionValidator;
use Cmuset\ChessTools\Tool\Validator\GameValidator;

$violations = (new PositionValidator())->validate($pos);   // PositionViolationEnum[]
$violation  = (new GameValidator())->validate($game);      // ?GameViolation
```

### Resolvers — [`docs/tools/resolver.md`](docs/tools/resolver.md)

[](#resolvers--docstoolsresolvermd)

Derive information absent from raw SAN: source squares, capture flags, and check/checkmate markers.

ClassResolves`MoveResolver`A single `Move` against a `Position``VariationResolver`All moves in a `Variation` sequentially`GameResolver`The full game main line + result detection```
use Cmuset\ChessTools\Tool\Resolver\GameResolver;

GameResolver::create()->resolve($game);
// All moves now carry squareFrom, isCapture, isCheck, isCheckmate
```

### VariationSplitter — [`docs/tools/splitter.md`](docs/tools/splitter.md)

[](#variationsplitter--docstoolssplittermd)

Extracts all nested variations into a flat list of independent `Variation` objects, each prefixed with the moves preceding the divergence.

```
$variations = $game->split(); // Variation[] — first is always the main line
```

### VariationMerger — [`docs/tools/merger.md`](docs/tools/merger.md)

[](#variationmerger--docstoolsmergermd)

Merges `Variation` objects back into a main line, inserting diverging moves as nested sub-variations at the correct branching points.

```
$game->merge($variationA, $variationB);
```

---

Model Overview
--------------

[](#model-overview)

 ```
classDiagram
    direction TB

    class Game {
        tags: array
        result: ResultEnum
        +fromPGN(string) Game
        +getPGN() string
        +split() Variation[]
        +merge(Variation[]) void
    }

    class Position {
        sideToMove: ColorEnum
        castlingRights: CastlingEnum[]
        enPassantTarget: CoordinatesEnum
        halfmoveClock: int
        fullmoveNumber: int
        +fromFEN(string) Position
        +getFEN() string
        +applyMove(Move) void
        +getLegalMoves() Move[]
    }

    class Variation {
        identifier: string
        +fromPGN(string) Variation
        +getPGN() string
        +split() Variation[]
        +merge(Variation[]) void
    }

    class MoveNode {
        moveNumber: int
        nags: int[]
        beforeMoveComment: string
        afterMoveComment: string
        +getKey() string
    }

    class Move {
        isCapture: bool
        isCheck: bool
        isCheckmate: bool
        +fromSAN(string, ColorEnum) Move
        +getSAN() string
    }

    class Square {
        +isEmpty() bool
    }

    Game "1" --> "1" Position : initialPosition
    Game "1" --> "1" Variation : mainLine
    Variation "1" *-- "*" MoveNode : nodes
    MoveNode "1" --> "0..1" Move
    MoveNode "1" *-- "*" Variation : variations
    Position "1" *-- "64" Square : squares
    Move --> PieceEnum
    Move --> CoordinatesEnum : to, squareFrom
    Move --> CastlingEnum
    Square --> PieceEnum
    Square --> CoordinatesEnum
```

      Loading ClassDescriptionDocs[`Game`](docs/models/game.md)Full PGN game: tags, initial position, main line, result[→](docs/models/game.md)[`Position`](docs/models/position.md)Board state: pieces, side to move, castling rights, en passant, counters[→](docs/models/position.md)[`Variation`](docs/models/variation.md)Ordered collection of `MoveNode` instances, keyed by `"1."` / `"1..."` notation[→](docs/models/variation.md)[`MoveNode`](docs/models/move-node.md)Node in the move tree: move + move number + comments + NAGs + sub-variations[→](docs/models/move-node.md)[`Move`](docs/models/move.md)Parsed SAN move: piece, destination, flags (capture, check, castling, promotion)[→](docs/models/move.md)[`Square`](docs/models/square.md)A board square: coordinates + optional piece[→](docs/models/square.md)---

Enums Reference
---------------

[](#enums-reference)

All domain concepts are PHP string-backed enums.

EnumValuesDocs[`ColorEnum`](docs/enums/color.md)`WHITE` `'w'` · `BLACK` `'b'`[→](docs/enums/color.md)[`PieceEnum`](docs/enums/piece.md)`WHITE_KING` `'K'` … `BLACK_PAWN` `'p'` (12 cases)[→](docs/enums/piece.md)[`CoordinatesEnum`](docs/enums/coordinates.md)`A1` `'a1'` … `H8` `'h8'` (64 cases)[→](docs/enums/coordinates.md)[`CastlingEnum`](docs/enums/castling.md)`WHITE_KINGSIDE` `'K'` · `WHITE_QUEENSIDE` `'Q'` · `BLACK_KINGSIDE` `'k'` · `BLACK_QUEENSIDE` `'q'`[→](docs/enums/castling.md)[`ResultEnum`](docs/enums/result.md)`WHITE_WINS` `'1-0'` · `BLACK_WINS` `'0-1'` · `DRAW` `'1/2-1/2'` · `ONGOING` `'*'`[→](docs/enums/result.md)---

Testing &amp; Development
-------------------------

[](#testing--development)

```
git clone https://github.com/clemuset/pgn-parser.git
cd pgn-parser
composer install
```

```
vendor/bin/phpunit                        # Run all tests
vendor/bin/phpunit tests/path/to/Test.php # Run a single test file
vendor/bin/phpunit --filter methodName    # Run a single test method

vendor/bin/phpstan analyse                # Static analysis (level 7)
vendor/bin/php-cs-fixer fix               # Fix code style
vendor/bin/php-cs-fixer fix --dry-run     # Check without applying changes
```

Or with Docker:

```
make check    # fix + analyse + test
make test
make analyse
make fix
```

---

MIT License. See `LICENSE`.

###  Health Score

42

—

FairBetter than 90% of packages

Maintenance86

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity59

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 66.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~16 days

Total

10

Last Release

73d ago

Major Versions

v1.3.0 → v2.0.02026-02-28

### Community

Maintainers

![](https://www.gravatar.com/avatar/26f13317a1f69b9c09a660cb59299574e4a80ffb213ea300a60d6738dc22e8a3?d=identicon)[cmuset](/maintainers/cmuset)

---

Top Contributors

[![clementmuset](https://avatars.githubusercontent.com/u/174725495?v=4)](https://github.com/clementmuset "clementmuset (16 commits)")[![clemuset](https://avatars.githubusercontent.com/u/45164919?v=4)](https://github.com/clemuset "clemuset (8 commits)")

---

Tags

validatorparsertoolchessPGNfensanapplier

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/cmuset-chess-tools/health.svg)

```
[![Health](https://phpackages.com/badges/cmuset-chess-tools/health.svg)](https://phpackages.com/packages/cmuset-chess-tools)
```

###  Alternatives

[seld/jsonlint

JSON Linter

1.3k217.8M205](/packages/seld-jsonlint)[respect/validation

The most awesome validation engine ever created for PHP

5.9k37.4M383](/packages/respect-validation)[composer/spdx-licenses

SPDX licenses list and validation library.

1.4k184.2M25](/packages/composer-spdx-licenses)[opis/json-schema

Json Schema Validator for PHP

64236.9M186](/packages/opis-json-schema)[laminas/laminas-validator

Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria

15544.9M188](/packages/laminas-laminas-validator)[ergebnis/json-schema-validator

Provides a JSON schema validator, building on top of justinrainbow/json-schema.

3626.9M7](/packages/ergebnis-json-schema-validator)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
