FEAST is designed to make working with databases easier for the developer. It contains tools for both introspection and data modeling to decrease the time spent writing boilerplate.
FEAST uses a simple configuration format for specifying Database connections. The Config.md contains details on the configuration file itself, but below is a sample for database configuration.
<?php
return [
'database.default' => [
'host' => 'hostname',
'name' => 'databaseName'
'user' => 'username'
'password' => 'password',
'url' => 'mysql:host=%s;port=%s;dbname=%s' // OPTIONAL: A manually specified connection string. If blank, the framework
// will build from the other parameters
'connectionType' => \Feast\Enums\DatabaseType::MYSQL,
'queryClass' => \Feast\Database\MySQLQuery::class,
// 'queryClass' => \Feast\Database\SQLiteQuery::class,
// 'connectionType' => \Feast\Enums\DatabaseType::SQLITE,
// 'connectionType' => \Feast\Enums\DatabaseType::POSTGRES,
// 'queryClass' => \Feast\Database\PostgresQuery::class,
'options' => [ // NOTE: the below options are not required. The ones below are applied by default.
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
PDO::ATTR_EMULATE_PREPARES => false
]
]
];
FEAST makes use of a Mapper/Model architecture to separate Database access from Data representation.
One of the most time-consuming parts of development is often creating the classes to interact with your database. With FEAST, this is no longer the case.
FEAST ships with a CLI tool for creating models/mappers via database introspection. To create a model/mapper pair,
simply run feast:create:model
. You can read more about this command
here.
All Mappers in FEAST extend from \Feast\BaseMapper
. This class contains many methods for working with data.
FEAST has many methods to retrieve records from the database. The main methods to fetch data are listed below.
BaseMapper::findByPrimaryKey
- This method takes a string or int parameter to look up and will return either the corresponding Model or null if not found.BaseMapper::findOneByField
- This method takes two arguments - a field name and a value. It will automatically convert this to awhere
clause and return either the first corresponding Model or null if not found.BaseMapper::findAllByField
- This method takes two arguments - a field name and a value. It behaves similarly tofindOneByField
except it returns a\Feast\Collection\Set
containing all the matching Models.BaseMapper::findOneByFields
- This method takes an array of arguments in key => value format. It will automatically convert this to awhere
clause matching all values, and return either the first corresponding Model or null if not found.BaseMapper::findAllByFields
- This method takes an array of arguments in key => value format. It behaves similarly tofindOneByField
except it returns a\Feast\Collection\Set
containing all the matching Models.
Examples:
<?php
$mapper = new TestMapper();
$model = $mapper->findByPrimaryKey(1); // Single model
$model = $mapper->findOneByField('name','Feast'); // Single model
$model = $mapper->findOneByFields(['name' => 'Feast', 'status' => 'active']); // Single model
$models = $mapper->findAllByField('name','Feast'); // \Feast\Collection\Set of models
$models = $mapper->findAllByFields(['name' => 'Feast', 'status' => 'active']); // \Feast\Collection\Set of models
To save a model, call either the save
method on the mapper, passing in the model as an argument or for
convenience, simply call the save()
method on the model. FEAST will automatically perform an insert if it is a new
model, or an update (passing only the changed fields) if it is was retrieved from the database.
You can force an update by passing in true
to the save
method as the only parameter on the model method, or as the
second parameter on the mapper method.
When the save method is called, the primary key is updated on the model for inserts.
Example:
<?php
$model = new \Model\Test();
$model->name = 'Feast';
$model->save(); // Model saves directly, inserted.
$model->name = 'NotFeast';
$mapper = new \Mapper\TestMapper();
$mapper->save($model); // Model saved indirectly after previous save, updated.
Force update
<?php
$model = new \Model\Test();
$model->id = 1;
$model->name = 'Feast';
$model->save(true); // Model saves directly, inserted.
$model->name = 'NotFeast';
$mapper = new \Mapper\TestMapper();
$mapper->save($model, true); // Model saved indirectly after previous save, updated.
FEAST has many methods to delete records from the database. The main methods to delete data are listed below.
BaseMapper::deleteByFields
- This method takes an array of arguments in key => value format. It will automatically convert this to awhere
clause matching all values, and will delete any matching records. It will return the deleted record count.BaseMapper::deleteByField
- This method takes two arguments - a field name and a value. It will automatically convert this to awhere
clause and will delete any matching records. It will return the deleted record count.BaseMapper::delete
- This method takes a Model as an argument and will delete the corresponding record based on the primary key. It will return either 0 or 1 (the count deleted from the database).
FEAST has methods for different database events that you can override in the child class. These events can fire when a
model is saved or when a model is deleted. The methods are onSave
and onDelete
.
Note that onDelete
will only fire if delete
is called, rather than the other deletion methods, as delete is the only
method that has access to a model.
FEAST has a robust Migration system to allow programmatic database changes to ensure consistency upon deployment.
Migrations can be created quickly and easily with the feast:migration:create
command. See
details here. Migrations can be used both for creating tables and altering them.
Migrations have both an up
and down
method. The up should be used for bringing your database up to date, and the
down should undo whatever is done in the up call.
FEAST provides a TableFactory to retrieve an instance of a table builder. Currently, this table builder is limited to MySQL and PostgreSQL. Using this table builder, you can quickly specify your table details and run the create without writing a single line of SQL. Note, however, that this is designed for use on simple structures and may not work in all use cases.
Example table builder usage:
public function up() : void
{
TableFactory::getTable('videos')
->autoIncrement('id')
->varChar('title')
->text('description')
->varChar('heading')
->text('block_text')
->varChar('video')
->create();
parent::up();
}
public function down() : void
{
/** @todo Create down query */
TableFactory::getTable('videos')->drop();
parent::down();
}
The create
method will create the table, drop
will drop the table, and dropColumn
will drop the specified column.
There are many other methods available to the Table instance returned by the Table factory for defining columns.
int
tinyInt
smallInt
mediumInt
bigInt
float
double
decimal
varChar
char
tinyText
text
mediumText
longText
tinyBlob
blob|bytea
- (for MySQL and PostgreSQL respectively)mediumBlob
longBlob
date
datetime
timestamp
time
json
serial
- (throws an exception if used on a MySQL table)boolean
- (alias to tinyint(1) for MySQL table)column
- Column is used if you need a column type that does not fit into the other rules.
For the MySQL database engine, the following methods also exist for the Table builder
dbEngine
- Specifies the database engine.characterSet
- Specifies the character set for the table.collation
- Specifies the collation for the table.
In addition, the rawQuery
method can be used in a migration to run a specific query or a more complex CREATE.
FEAST can add an index at the same time as creating by using the index
method. The index
method takes the following
parameters.
columns
- This can be a string for a single column, or an array of strings for multiple.name
- String or null. If null, a generic name is dynamically created.autoIncrement
- True if you wish for this to be an autoincrement column.
In MySQL, an easier way to create an auto incrementing primary key is with the autoIncrement
method. This method will
create an
int column with the passed in name and optional length.
In PostgreSQL, an easier way to create an auto incrementing primary key is to call the serial
method rather than an
integer when creating the table.
For example:
TableFactory::getTable('videos')
->serial('id');
FEAST can add a unique index at the same time as creating by using the uniqueIndex
method. This method takes the
following parameters.
columns
- This can be a string for a single column, or an array of strings for multiple.name
- String or null. If null, a generic name is dynamically created.
FEAST can add a foreign key at the same time as creating the table by using the foreignKey
method. This method takes
the following parameters.
columns
- This can be a string for a single column, or an array of strings for multiple.referencesTable
- The table to reference.referencesColumns
- This can be a string for a single column, or an array of strings for multiple.onDelete
- Defaults toRESTRICT
onUpdate
- Defaults toRESTRICT
name
- String or null. If null, a generic name is dynamically created.
FEAST can add a primary key to specified column with the primary
method. The primary
method takes only one
parameter:
columnName
. Note that the column specified within this parameter must exist. Also, primary
method can be called only
once per table. Otherwise, an exception will be thrown.
The autoIncrement
method and serial
method both already call the primary
method, so the primary
method should
not be called when
creating an auto incrementing column with the autoIncrement
method.
FEAST can alter tables by using the rawQuery
method rather than by using the TableFactory.
To quickly run all migrations that have not ran up, simply run
php famine feast:migration:run-all
in your terminal. For more detailed or advanced usage,
see feast:migration in the CLI docs.
If you have cached your database info (see feast:cache:dbinfo-generate), then the cache will automatically re-generate after migrations are ran.
You can quickly get a list of all migrations as well as their status by running php famine feast:migration:list
To run complex queries on your data, several additional methods exist on the DatabaseInterface that can be used inside your Mappers. These methods return a Query instance which has even more methods for interacting with your database.
The five basic methods in the DatabaseInterface
are select
,update
,insert
, replace
and delete
. Each of these
methods takes a table name as the argument, and update
,insert
, and replace
take an array of parameters to be
inserted/updated.
In addition, the DatabaseInterface has several transactional based methods if you need to run a set of queries in a transaction
beginTransaction
- This will begin a transaction and return true if successful, false on failure or if already in a transaction.isInTransaction
- This method will return true if in a transaction or false otherwise.commit
- This will commit the current transaction's changes to the database.rollback
- This will rollback (or abort) the changes for the current transaction.
The Query
class contains several methods for working with your database and allows for fine-tuned queries without
writing any SQL. The methods can be called in any order, and the executed query will be ordered correctly.
All bindings in the below methods are passed in as a prepared statement execution.
The where
method creates a where clause on the query. It takes in a statement (or the where clause)
and bindings as either a \Feast\Date
argument, or a scalar. Multiple bindings may be passed in as needed. In versions
of FEAST Framework prior to 2.0, multiple bindings are passed in as an array. Each call to the where
method will create
a parenthesis wrapped group, allowing you to focus only on what you need for that piece of the statement.
Example
$query->where('test = ? or test_name = ?', ['feast','feast'])->where('active' => 1); // In version 1.x
$query->where('test = ? or test_name = ?', 'feast','feast')->where('active' => 1); // In version 2.0.0 and above
// This will result in the following where clause on the query.
// where (test = ? or test_name = ?) and (active = ?)
The having
method is semantically identical to the where
method described above, but places a having clause instead
of a where clause.
The groupBy
method takes an SQL excerpt as a parameter and creates a
group by
clause on the query.
The limit
method takes the number to limit to and an optional offset as parameters. Example:
$query->limit(5,15)
// Adds LIMIT 5,15 to the SQL query
The orderBy
method takes an SQL excerpt as a parameter and creates an order by clause on the SQL query.
FEAST has several methods on the query class for joins. Two each for left, right, and inner joins.
Each of the Join methods (leftJoin
, rightJoin
, and innerJoin
) take three arguments. The first is the table to join
to, the second is the column or columns (string or array) in the parent table to join on, and the third is the column or
columns (string or array) on the joined table to join on.
Each of the Join using methods (leftJoinUsing
, rightJoinUsing
, and innerJoinUsing
)
take two arguments - The table name and the column or columns (string or array) that exists on both tables to join on.
Sometimes when developing, it helps to see the query as it would be executed to check if anything seems incorrect or out
of place. The Query
class has a method getRawQueryWithParams
that returns a string representation of the query with
all ? bindings replaced with their appropriate value.