Skip to content

feat: add query builder #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"files": [ "src/classnames/function.php" ],
"psr-4": {
"ClassNames\\": "src/classnames",
"MergeCoverage\\": "src/merge-coverage"
"MergeCoverage\\": "src/merge-coverage",
"QueryBuilder\\": "src/query-builder"
}
},
"require-dev": {
Expand Down
3 changes: 3 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
<testsuite name="classnames">
<directory>src/classnames/Tests</directory>
</testsuite>
<testsuite name="query-builder">
<directory>src/query-builder/Tests</directory>
</testsuite>
</testsuites>

<source restrictDeprecations="true" restrictNotices="true" restrictWarnings="true">
Expand Down
20 changes: 20 additions & 0 deletions src/query-builder/Column.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace QueryBuilder;

class Column
{
public function __construct(
private string $expression,
private ?string $alias = null
) {
}

public function __toString()
{
if ($this->alias) {
return $this->expression . ' AS ' . $this->alias;
}
return $this->expression;
}
}
22 changes: 22 additions & 0 deletions src/query-builder/From.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace QueryBuilder;

use QueryBuilder\Join\Equals;

class From
{
public function __construct(private string $expression)
{
}

public function join(string $table, Equals $condition )
{
$this->expression .= PHP_EOL . Query::INDENT . 'JOIN ' . $table . ' ON ' . (string) $condition;
}

public function __toString()
{
return 'FROM ' . $this->expression;
}
}
20 changes: 20 additions & 0 deletions src/query-builder/Join.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace QueryBuilder;

class Join
{
public function __construct(private string $table)
{
$this->table = $table;
}

private function plop($from, $join) {

}

public function __toString()
{
return Query::INDENT . "JOIN " . $this->table . $this->conditions();
}
}
17 changes: 17 additions & 0 deletions src/query-builder/Join/Equals.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace QueryBuilder\Join;

use QueryBuilder\Column;

class Equals
{
public function __construct(private Column $left, private Column $right)
{
}

public function __toString()
{
return $this->left . ' = ' . $this->right;
}
}
34 changes: 34 additions & 0 deletions src/query-builder/Query.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace QueryBuilder;

class Query
{
const INDENT = " ";

const FINAL_SEMI_COLON = ";" . PHP_EOL;

private $select;
private $from;
private $where;

public function __construct(Select $select, From $from, Where $where)
{
$this->select = $select;
$this->from = $from;
$this->where = $where;
}

public function __toString()
{
$parts = [
$this->select,
$this->from,
"WHERE " . $this->where,
Query::FINAL_SEMI_COLON,
];

return implode(PHP_EOL, $parts);
}
}

24 changes: 24 additions & 0 deletions src/query-builder/Select.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace QueryBuilder;

class Select
{
private array $columns;

public function __construct(Column ...$columns)
{
$this->columns = $columns;
}

public function add(Column $column){
$this->columns[] = $column;
return $this;
}

public function __toString()
{
$select = implode("," . PHP_EOL . Query::INDENT, $this->columns);
return "SELECT" . PHP_EOL . Query::INDENT . $select;
}
}
21 changes: 21 additions & 0 deletions src/query-builder/Sum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace QueryBuilder;

class Sum extends Column
{
public function __construct(
private string $expression,
private ?string $alias = null
) {
$this->expression = 'SUM(' . $this->expression . ')';
}

public function __toString()
{
if ($this->alias) {
return $this->expression . ' AS ' . $this->alias;
}
return $this->expression;
}
}
31 changes: 31 additions & 0 deletions src/query-builder/Table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace QueryBuilder;

class Table
{
public function __construct(protected string $name, private ?string $alias = null)
{
}

public function from() {
return new From($this->alias ? $this->name . ' ' . $this->alias : $this->name);
}

public function column(string $expression, ?string $alias = null)
{
$table = $this->alias ?? $this->name;
return new Column($table . '.' . $expression, $alias);
}

public function sum(string $expression, ?string $as = null)
{
$table = $this->alias ?? $this->name;
return new Sum($table . '.' . $expression, $as);
}

public function __toString()
{
return $this->alias ?? $this->name;
}
}
91 changes: 91 additions & 0 deletions src/query-builder/Tests/QueryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);

namespace QueryBuilder\Tests;

use PHPUnit\Framework\TestCase;
use QueryBuilder\From;
use QueryBuilder\Join\Equals;
use QueryBuilder\Query;
use QueryBuilder\Select;
use QueryBuilder\Table;
use QueryBuilder\Where;

class QueryTest extends TestCase
{
public function testQuery()
{
$customers = new class('customers') extends Table {
public function name() {
return $this->column('name');
}
public function id() {
return $this->column('id');
}
public function departement() {
return $this->column('departement');
}
public function departementShouldBe(int $departement) {
return new Where\Equals($this->departement(), (string) $departement);
}

public function joins(Table $orders){
return $orders->from()->join($this->name, new Equals($orders->customerId(), $this->id()));
}
};

$orders = new class('orders') extends Table {
public function customerId() {
return $this->column('customer_id');
}
public function id() {
return $this->column('id');
}
public function valid() {
return $this->column('valid');
}
public function shouldBeValid() {
return new Where\Equals($this->column('valid'), 'TRUE');
}
public function total() {
return $this->sum('price',as: 'total');
}
};

$customerOrders = new class($orders, $customers) extends From {
public function __construct($orders, $customers)
{
parent::__construct('orders');
$this->join('customers', new Equals($orders->customerId(), $customers->id()));
}
};

$select = new Select(
$orders->total(),
$customers->name(),
);

$validDepartements = Where::atLeastOne(
$customers->departementShouldBe(14),
$customers->departementShouldBe(15),
);
$where = Where::everyOnes(
$orders->shouldBeValid(),
$validDepartements,
);

$query = new Query($select, $customerOrders, $where);

$sql = <<<SQL
SELECT
SUM(orders.price) AS total,
customers.name
FROM orders
JOIN customers ON orders.customer_id = customers.id
WHERE orders.valid = TRUE AND (customers.departement = 14 OR customers.departement = 15)
;

SQL;
self::assertSame($sql, (string) $query);
}
}
36 changes: 36 additions & 0 deletions src/query-builder/Where.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php


namespace QueryBuilder;

use QueryBuilder\Where\Ensemble;

class Where
{
public function __construct(private string $expression)
{
}

public static function atLeastOne(self ...$wheres): self {
foreach ($wheres as &$where) {
if ($where instanceof Ensemble) {
$where = '(' . $where . ')';
}
}
return new Ensemble(implode(' OR ', $wheres));
}

public static function everyOnes(self ...$wheres): self {
foreach ($wheres as &$where) {
if ($where instanceof Ensemble) {
$where = '(' . $where . ')';
}
}
return new Ensemble(implode(' AND ', $wheres));
}

public function __toString(): string
{
return $this->expression;
}
}
19 changes: 19 additions & 0 deletions src/query-builder/Where/Ensemble.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace QueryBuilder\Where;

use QueryBuilder\Column;
use QueryBuilder\Where;

class Ensemble extends Where
{
public function __construct(private string $expression)
{
}

public function __toString(): string
{
return $this->expression;
}
}

19 changes: 19 additions & 0 deletions src/query-builder/Where/Equals.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace QueryBuilder\Where;

use QueryBuilder\Column;
use QueryBuilder\Where;

class Equals extends Where
{
public function __construct(private Column $left, private string $right)
{
}

public function __toString(): string
{
return $this->left . ' = ' . $this->right;
}
}