Repository Class
The purpose of the Repository is to separate the logic that retrieves data from the persistence layer from the models themselves and the business logic that acts on those models. The Repository acts as a sort of controller that utilizes a Gateway for direct access to data and a Factory for creating objects.
Creating a Repository
There's a few different ways to get a Repository depending on the situation. The most general purpose and easiest solution is usually to utilize the RepositoryFactory class. However, for situations where you need to utilize a custom Gateway or Factory, creating a Repository without using the factory may be desired. Finally, if you already have an instance of a model, you can ask a model to give you a repository matching its type.
Using RepositoryFactory
For simple class namespaces, it's easiest to just type the name in like so:
class MyController extends \MyApp {
public function demo()
{
$repo = \Cora\RepositoryFactory::make('User');
$user = $repo->find($id);
}
}
For more advanced namespaces, you can take advantage of Use statements by utilizing the "class" keyword:
use \System\Blog\Bug\Task\Note;
class MyController extends \MyApp {
public function demo()
{
$repo = \Cora\RepositoryFactory::make(Note::class);
$user = $repo->find($id);
// OR you can still do
$repo = \Cora\RepositoryFactory::make('\\System\\Blog\\Bug\\Task\\Note');
$user = $repo->find($id);
}
}
RepositoryFactory Options
While we're on the topic of using the RepositoryFactory, now is a good time to look at its make() method definition and the options it gives you.
make($class, $idField = false, $table = false, $freshAdaptor = false, $db = false)
- The $class parameter is the object type you'll be using the repository for.
- The optional $idField parameter is for setting the unique ID field which will be used to fetch objects from a database. You'll most likely want to set this if your ID column is something other than 'id'.
- The optional $table parameter is for overriding the defaults as defined in a model. If this parameter is defined, it will be passed to the Gateway as the table/collection to fetch objects from.
- The optional $freshAdaptor is a setting you'll most likely not ever need to use. See my explanation after this list for more details.
- The optional $db let's you pass in a custom Cora database connection for the Gateway to use. This can be used to force the Repository and Dynamic Loading to use a connection to a Test database for testing purposes.
Explanation of $freshAdaptor:
ADM is built using Cora's Database class, and since most apps probably only use a single database, part of the database setup process is setting a "default connection". When fetching models, if no particular connection is specified in the model definition, ADM will default to using the default connection. In order to make this typical use case more efficient, the Database class stores an instance of the default DB as a static data member. When a database operation involving the default database is performed, this statically stored default will be used rather than creating a new instance each time. In most cases this is great! However, because Cora's Database class can only handle building 1 query at a time, if you end up in a situation where in the middle of building a query, you need to pause and perform another query on the default connection, this can cause problems. In this scenario, what you need is a fresh version of the adaptor and not the statically stored one. Setting $freshAdaptor to true gives you a new database class.
I realize that explanation is a little hard to follow, but the reason for the $freshAdaptor parameter's existence has to do with the inner optimization workings of Cora, and is inherently complicated for that reason. The good news is that unless you start building your own custom gateway classes, I can't really think of a reason you'd need to use or understand it.
The backstory on this feature is that Cora's Gateway walks through a loop of a model's attributes and builds up the query to save the model to the database. However, when a related object is encountered, it recursively calls the Save() method on that related object at the time it encounters it. Because the default adaptor was already being used to build up a query, that recursive call to the Save() method on the related object was causing conflicts if the related object also used the default adaptor (due to the one query at a time restriction on a Cora Database class and the default database being stored statically).
Using Custom Setup
For situations where you want to swap out components of the repository system with your own custom classes, you'll need to bypass the RepositoryFactory and just do the setup yourself. The general format is as follows:
$db = \Cora\Database::getDb('name_of_db_connection_to_use');
$class = 'User';
$factory = new Factory($class, $db);
$gateway = new Gateway($db);
$repo = new Repository($gateway, $factory);
Using Model Method
If you're in a situation where you created an instance of a Cora Model class, and now you need a repository for it to persist it or whatever, you can take advantage of Cora Model's knowledge on how to create repositories for themselves:
$user = new \User('Josiah', 'SuperAdmin');
$user->location = new Location('JoesHouse', 'Portland');
$user->job = new Job('Librarian', 'Keeper of knowledge!');
$repo = $user->getRepository();
$repo->save($user);
Using a Repository
find() Method
Purpose:
The find() method is for grabbing a single object from a database based on the
object's unique ID.
Format:
find(id)
Examples:
// Create Repository
$repo = \Cora\RepositoryFactory::make('User');
// Grab a user Object
$user = $repo->find($id);
findOne() Method
Purpose:
The findOne() method is for grabbing a single object from a database based on
the passed in query parameters.
Format:
findOne([function | coraDbQuery])
Examples using a function:
Basic Example:
// Create Repository
$repo = \Cora\RepositoryFactory::make('User');
// Fetch a user using a closure
$user = $repo->findOne(function($query) {
return $query->where('name', 'testUser');
});
Passing in a variable:
// Create Repository
$repo = \Cora\RepositoryFactory::make('User');
// Fetch a user using a closure and pass in a variable
$user = $repo->findOne(function($query, $name) {
return $query->where('name', $name);
}, 'testUser');
Using a simple LoadMap:
// Create Repository
$repo = \Cora\RepositoryFactory::make('User');
// Create LoadMap which will cause 'email' field from database
// to load into the 'name' field on the User model and also
// load the 'primaryRole' model relationship right away.
$loadMap = new \Cora\Adm\LoadMap([
'email' => 'name'
], [
'primaryRole' => true
]);
// Fetch a user using a closure and pass in a variable and a LoadMap
$user = $repo->findOne(function($query, $name) {
return $query->where('name', $name);
}, 'testUser', $loadMap);
Examples using query object:
// Create Repository
$repo = \Cora\RepositoryFactory::make('User');
// Grab an instance of database adaptor for this model.
$db = $repo->getDb();
// Define some db query parameters.
$db->where('name', 'testUser');
// Grab a user Object matching query.
$user = $repo->findOne($db);
findAll() Method
Purpose:
The findAll() method is for grabbing all objects from a database collection.
The results can be limited by optionally passing along query parameters.
Format:
findAll([function | coraDbQuery])
Examples using a function:
Basic Example:
// Create Repository
$repo = \Cora\RepositoryFactory::make('User');
// Fetch users using a closure
$users = $repo->findAll(function($query) {
return $query->where('type', 'Admin');
});
Passing in a variable:
// Create Repository
$repo = \Cora\RepositoryFactory::make('User');
// Fetching users using a closure and pass in a variable
$users = $repo->findAll(function($query, $type) {
return $query->where('type', $type);
}, 'Admin');
Using a simple LoadMap:
// Create Repository
$repo = \Cora\RepositoryFactory::make('User');
// Create LoadMap which will cause 'email' field from database
// to load into the 'name' field on the User models and also
// load the 'primaryRole' model relationship right away on all models.
$loadMap = new \Cora\Adm\LoadMap([
'email' => 'name'
], [
'primaryRole' => true
]);
// Fetch users using a closure and pass in a variable and a LoadMap
$users = $repo->findAll(function($query, $type) {
return $query->where('type', $type);
}, 'Admin', $loadMap);
Examples using query object:
// Create Repository
$repo = \Cora\RepositoryFactory::make('User');
// Fetch ALL users.
$allUsers = $repo->findAll();
// Grab an instance of database adaptor for this model.
$db = $repo->getDb();
// Define some db query parameters.
$db->where('type', 'Admin');
// Grab a subset of users. In this case all users who are Admins.
$admins = $repo->findAll($db);
findBy() Method
Purpose:
The findBy() method is for grabbing a set of objects based on a simple restriction.
This has the same basic functionality as findAll(), but is simpler to use with
the tradeoff of being less flexible.
Format:
// options = array(['order_by' => 'desc|asc'] [, 'limit' => number [, 'offset' => number]])
findBy(property, value [, options])
Because the "options" is kinda hard to read above let me just verbally describe it. You can pass in an associative array with up to 3 keys, those being "order_by", "limit", and "offset". Order_by should be either "desc" or "asc", and limit and offset should be numbers. Offset doesn't do anything unless you also pass in a Limit.
Examples:
// Create Repository
$repo = \Cora\RepositoryFactory::make('User');
// Grab users who are Admins.
$admins = $repo->findBy('type', 'Admin');
// Grab users who are Moderators, but only return the first 10.
$mods = $repo->findBy('type', 'Moderator', ['limit' => 10]);
// Grab the next 10 Moderators
$moreMods = $repo->findBy('type', 'Moderator', ['limit' => 10, 'offset' => 10]);
delete() Method
Purpose:
The delete() method is for deleting a single object from a database based on the
object's unique ID. It will call the object's own "delete" method for cleanup
before removing the record from the database.
Format:
delete(id)
Examples:
// Create Repository
$repo = \Cora\RepositoryFactory::make('User');
// Delete a user.
$repo->delete($id);
save() Method
Purpose:
The save() method is for persisting one or more objects. Usually this means saving
records in a database. If the object(s) already exists in the database, then
any changes will be updated. If the object(s) does not exist, then it will be
inserted.
Format:
save(Cora_Model | ResultSet_of_Cora_Models [, table] [, id_name])
The Table and Id_name optional parameters are for overriding default model behavior if necessary. The refer to the table/collection the object should be saved to, and the unique identifying column for the object. You probably don't need to use these! Check out the RepositoryFactory options near the top of this page for a way you can set these options for all operations on a Repository.
Examples:
// Create Repository
$repo = \Cora\RepositoryFactory::make('User');
/////////////////////////////////////////////
// Modify a single existing user.
/////////////////////////////////////////////
// Grab a user Object
$user = $repo->find($id);
// Change something
$user->email = 'BobsNewEmail@fakeEmail.com';
// Save the changes.
$repo->save($user);
////////////////////////////////////////////
// Save a completely new user.
////////////////////////////////////////////
// Create a user object.
$user = new User('Bob', 'Bob@fakeEmail.com');
// Save the user to the database.
$repo->save($user);
////////////////////////////////////////////
// Modify a collection of users.
// We'll pretend we're crediting our 'premium'
// users some amount to their account.
////////////////////////////////////////////
// Grab a collection of users.
$users = $repo->findBy('type', 'Premium');
// Change something
foreach ($users as $user) {
$user->accountCredit += 100;
}
// Save the changes.
$repo->save($users);
////////////////////////////////////////////
// Assign a collection of objects to a user.
////////////////////////////////////////////
// Grab a user Object
$user = $repo->find($id);
// Assign a collection of articles to the user
$user->articles = new \Cora\ResultSet([
new Article('How to Fly a Plane'),
new Article('How to Make Money')
]);
// Save the changes.
$repo->save($user);
////////////////////////////////////////////
// Add to an existing collection of objects
////////////////////////////////////////////
// Grab a user Object
$user = $repo->find($id);
// Add a new article to the collection of articles this user has.
$user->articles->add(new Article('How to Make Money Vol2'));
// Save the changes.
$repo->save($user);