Freckle is an Object-Relational-Mapper built on top of Doctrine DBAL.
Freckle is inspired by Spot2.
Install with Composer
composer require marcojetson/freckle
You can get a connection through the Freckle\Manager
class.
$connection = Freckle\Manager::getConnection([
'driver' => 'pdo_sqlite',
]);
Entities must extend Freckle\Entity
and implement the definition
method.
/**
* @method int getId()
*
* @method string getTitle()
* @method setTitle(string $title)
*
* @method string getBody()
* @method setBody(string $body)
*/
class Post extends Freckle\Entity
{
public static function definition()
{
return [
'table' => 'post',
'fields' => [
'id' => ['integer', 'sequence' => true, 'primary' => true],
'title' => 'string',
'body' => 'string',
],
];
}
}
Defining an entity requires a table name and its fields. Fields are defined by an array with mandatory positional parameters and optional named parameters.
[
string $type,
mixed default=null, // default value, callables supported!
bool primary=false,
bool require=false,
bool|string sequence=false, // specify sequence name if required by database
]
Freckle is able to generate entities for you. Use Freckle\Connection::import()
to automatically generate mappings for your tables.
foreach ($connection->generate() as $mapping) {
file_put_contents($mapping->entityClass() . '.php', (string)$mapping);
}
Interact with your entities using a mapper. You can get a mapper using the previously created connection.
$postMapper = $connection->mapper(Post::class);
// create entity and insert
$post1 = $postMapper->entity([
'title' => 'Hello World',
'body' => 'My very first post',
]);
$postMapper->insert($entity);
// ...or do it in a single step
$post2 = $postMapper->create([
'title' => 'Lorem Ipsum',
'body' => 'My second post',
]);
$post2->setTitle('Lorem ipsum dolor');
$postMapper->update($post2);
Not sure if new entity or not? Then use Freckle\Mapper::save()
.
$postMapper->delete($post2);
Use Freckle\Mapper::find()
to initialize a query
$query = $postMapper->find(['title like' => '% post']);
// queries are lazy, keep attaching parts till ready
$query->not('id', 1)->gte('score', 10);
foreach ($query as $post) {
echo $post->getName(), PHP_EOL;
}
// or retrieve a single result
$postMapper->find(['id' => 1])->first();
Where operators can be appended to field when using Freckle\Query::where()
or being executed as query methods.
- eq, equals, =
- not, !=
- gt, greaterThan, >
- gte, greaterThanOrEquals, >=
- lt, lessThan, <
- lte, lessThanOrEquals, <=
- like
Add your own operators extending Freckle\Operator
.
class JsonExists extends Operator
{
public function __invoke(Query $query, $column, $value = null)
{
return 'jsonb_exists(' . $column . ', ' . $query->parameter($value) . ')';
}
}
Freckle\Operator::add('json_exists', JsonExists::class);
$postMapper->find([
'properties json_exists' => 'author',
]);
// or use it as a method
$postMapper->find()->json_exists('properties', 'author');
Related entity retrieval is supported.
/**
* @method int getId()
*
* @method string getBody()
* @method setBody(string $body)
*
* @method int getPostId()
* @method setPostId(int $postId)
*
* @method Post getPost()
*/
class Comment extends Freckle\Entity
{
public static function definition()
{
return [
'table' => 'comment',
'fields' => [
'id' => ['integer', 'sequence' => true, 'primary' => true],
'body' => 'string',
'post_id' => 'integer',
],
'relations' => [
'post' => ['one', Post::class, ['id' => 'this.id']],
},
],
];
}
}
In the same fashion of fields, defining a relation consist in an array with mandatory positional parameters and optional named parameters.
[
string $type,
string $entityClass,
array $conditions,
string through=null, // "table.column" for many-to-many relations
string field='id', // related entity primary column
]