In my previous post I mentioned thinking about generating a query from code. And my example was the following.
new Query(
new Select(
new Fields(
new Field(Pages::Id),
new Field(Pages::Name),
new Field(Pages::Url),
),
new From(Pages::Pages),
new Where(
new EqualParameter(Pages::Id),
)
)
)
To get it out of my mind, I created a fast solution using the Stringable interface.
The base classes
abstract readonly class StringMultiple implements Stringable
{
protected array $classes;
public function __construct(Stringable ...$classes)
{
$this->classes = $classes;
}
public function __toString(): string
{
$classes = array_map(fn ($class) => (string) $class, $this->classes);
return join(' ', $classes);
}
}
This is the base class for all classes that can contain other classes.
The beauty of this is that PHP takes care of the deeper levels on the graph by calling the __toString method on every class.
abstract readonly class StringSingle implements Stringable
{
public abstract function __toString(): string;
}
This is the base class for classes that don't contain other classes.
Most of the time these classes will have a different constructor.
The query classes
readonly class Query extends StringMultiple
{}
The Query class is there to start building the string. That is why it has nothing in the body of the class.
readonly class Select extends StringMultiple
{
public function __toString(): string
{
return 'SELECT ' . parent::__toString();
}
}
This adds SELECT in front of the rest of the string.
readonly class Fields extends StringMultiple
{
public function __toString(): string
{
$fields = array_map(fn ($class) => (string) $class, $this->classes);
return join(', ', $fields);
}
}
The fields class takes care of the comma's between the fields.
readonly class Field extends StringSingle
{
public function __construct(private Identifier $field)
{}
/**
* @inheritDoc
*/
public function __toString(): string
{
return queryStringFromIdentifier($this->field);
}
}
I'm using Identifier and queryStringFromIdentifier from the Idable queries core library to transform the enum case to the database field.
readonly class From extends StringSingle
{
public function __construct(private Identifier $field)
{}
public function __toString(): string
{
return 'FROM ' . queryStringFromIdentifier($this->field);
}
}
The same Identifier code, with the addition of prefixing the string with FROM.
readonly class Where extends StringMultiple
{
public function __toString(): string
{
return 'WHERE ' . parent::__toString();
}
}
I guess you can already see the pattern at this point.
readonly class EqualParameter extends StringSingle
{
public function __construct(private Identifier $identifier)
{}
public function __toString(): string
{
$querystring = queryStringFromIdentifier($this->identifier);
return "$querystring = :$querystring";
}
}
Now I have all the classes to create the query I showed in the example.
So all that it needs to get the query as a string is to add (string) in front of the object graph.
Of course there are still a lot of things that need to be done to have all the options SQL provides.
But now I won't give it as much thought as I did before. And I can focus on the other database packages for the Idable queries library family.
Top comments (0)