I'm at a stage where I'm refactoring my code, and I've come across an interesting conundrum.
In my ArticleController I have a bog standard store method for storing an article in my articles database table.
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(StoreArticle $request)
{
$article = new Article();
$defauultPublished = "draft";
$IntranetOnly = false;
$isFeatured = false;
$isFeatured = ($request->get('featuredArticle') == "1" ? true : false);
$IntranetOnly = ($request->get('IntranetOnly') == "1" ? true : false);
$article->title = $request->get('title');
$article->slug = str_slug($request->get('title'));
$article->author = $request->get('author');
$article->category = $request->get('category');
$article->excerpt = $request->get('excerpt');
$article->content = clean($request->get('content'));
$article->featuredImage = $request->get('featuredImage');
$article->featuredVideo = $request->get('featuredVideo');
$article->readingTime = $this->calculateReadTime($request);
$article->featuredArticle = $isFeatured;
$article->IntranetOnly = $IntranetOnly;
$article->published = $defauultPublished;
$article->save();
$article->handleTags($request);
return redirect('editable/news-and-updates')->with('success', 'Article has been added');
}
I also have a function for calculating read time:
/**
* Calculate a rough reading time for an articles by counting the words present
* These words are then divided by a given reading time and rounded to the nearest whole number
* Reading time average is roughly 267 words per minute, so this also accounts for relatively slow readers
*
* #param Request $request
* #return void
*/
public function calculateReadTime(Request $request)
{
$readingSpeed = 200;
$title = str_word_count(strip_tags($request->get('title')));
$excerpt = str_word_count(strip_tags($request->get('excerpt')));
$content = str_word_count(strip_tags($request->get('content')));
$words = ($title + $excerpt + $content);
$minutes = round($words / $readingSpeed);
return $minutes . ' minute' . ($minutes == 1 ? '' : 's');
}
My question is should these methods be moved to the Article model?
Controller should be as slim as possible. Following a resourceful approach (which you seem to be doing), the store() method in your ArticleController class should strive as much as possible to look like this:
class ArticleController extends Controller
{
public function store(CreateArticleRequest $request)
{
$article = Article::create($request->validated());
// Redirect with success message
}
}
Here, your request data is validated in a form request class before it even reaches the controller method; and then an Article model instance is created from that validated data.
A couple of other notes…
Statements like ($data['featuredArticle'] == "1" ? true : false) are overly verbose. You’re doing a condition check which will evaluate to true or false; you don’t need to manually return each value in a ternary operator. So this could be slimmed down to $data['featuedArticle'] == '1'. Furthermore, if you pass a value of 0 by default, then you could just get rid of the check entirely. If in your Blade template you put a hidden input before your checkbox:
<input type="hidden" name="featuredArticle" value="0" />
<input type="checkbox" name="featuredArticle" value="1" />
Then 1 will be send if the checkbox is checked (as it overrides the hidden input’s value, or 0 sent if the checkbox isn’t checked).
Also, try to stick to Laravel conventions to make your life easier. If you use snake_case for your input names, then it just makes life easier matching them up to model attribute and table column names. So use featured_article, have an attribute in your model with the same name, which maps to a database column with the same name again. This allows you to do shorthand calls like create() (as per my controller example) and update().
Finally, methods like calculating reading time definitely belong on your model. Models represent something in your application. It therefore follows that you can do things with your models. Calculating the time to read an Article model instance therefore lends itself to having a calculateReadingTime() method on the Article model.
A bit long-winded, but hopefully there should be some helpful pointers for you in the above. I’ve been working on Laravel projects for around five years now and have found that this approach and conventions is what works best.
Your controller's store article is fine, because it fills your article instance based on request data. It could use some refactoring and you could encapsulate more logic into your Article (for example, assign slug field inside your Article model whenever title is changed and so on).
But the line $article->handleTags($request); is a suspect, because your model should never operate with requests - it will quickly polute your model code with very specialized dependencies that you don't want (what happens when you receive your tags from cache and don't have a request instance? What happens if other type of request contains tags differently? and so on). Your model shouldn't have knowledge about requests or other parts of your app. Your controller is connecting the dots between them, so make sure your handleTags takes some basic abstract types/structures as a parameter (for example, an array) and make sure your controller takes and transforms data from request accordingly before feeding it to your article.
As for your calculateReadTime dilemma, it should definitely be inside your model. Think about it this way - do you have everything you need to calculate read time of your article inside your Article model? The answer is yes, it's a property of an article object, doesn't matter if you store it in DB or calculate it off other properties. Make getReadTime method. You don't want a controller to compute something about your model because it will tie that logic to a specific place in your app which is bad (what happens when you need to calculate read time of an article in other controller? Other model? and so on).
Make sure you read about has and is concepts regarding object-oriented design, it will help you immensely.
I think you should move those assignments to a Service Class. You could also go ahead and create a repository class. This would thus become your code structure:
Controller -> Service -> Repository -> Model.
Doing this $article = new Article(); is bad. You will have a had time when writing a test for your controller store method.
I would suggest you do this:
Create a Service class, say ArticleService.php. Define a store method in it.
ArticleService.php
use Article;
class ArticleService {
protected $article;
public function __construct(Article $article){
$this->article = $article;
}
public function store(array $data){
$defauultPublished = "draft";
$IntranetOnly = false;
$isFeatured = false;
$isFeatured = ($data['featuredArticle'] == "1" ? true : false);
$IntranetOnly = ($data['IntranetOnly'] == "1" ? true : false);
$this->article->title = $data['title'];
$this->article->slug = str_slug($data['title']);
$this->article->author = $data['author'];
$this->article->category = $data['category'];
$this->article->excerpt = $data['excerpt'];
$this->article->content = clean($data['content']);
$this->article->featuredImage = $data['featuredImage'];
$this->article->featuredVideo = $data['featuredVideo'];
$this->article->readingTime = $data['reading_time'];
$this->article->featuredArticle = $isFeatured;
//Capital letter I? You should be consistent with your naming convention
$this->article->IntranetOnly = $IntranetOnly;
$this->article->published = $defauultPublished;
if($this->article->save()){
$this->article->handleTags($request);
return true;
}
return false;
}
}
And your Controller now becomes:
class ArticleController{
protected $articleService;
public function __construct(ArticleService $articleService){
$this->articleService = $articleService;
}
public function store(Request $request){
//Some Validation Logic
$readingTime = $this->calculateReadTime($request)
$data = array_merge(['reading_time' => $readTime], $request->all());
return $this->articleService->store($request->all());
}
}
I also see that you are not validating the incoming Request. You should always do that because you can/should never trust your users to always provide/input the right data. It is your duty to force them to do that. e.g I as your user might decide to enter my name in your email field. If you don't validate that data, you will end up with wrong data.
There is also the issue of individually assigning your request parameter to their corresponding Model attribute. I decided to leave it that way so as not to overload you with information.
In summary, just take a look at the following resources for more insight.
https://laravel.com/docs/5.1/quickstart-intermediate
https://laravel.com/docs/5.6/validation
In short, read up the whole Laravel documentation! Goodluck!
Related
I have a Laravel 7 project with the need of a lot of data transformation from the model to the views before being displayed.
I thought about using Laravel Accessors and use them directly in my blade.php files.
But when I finished dealing with a simple html table, I looked at my code and I think there is too many accessors and even some of them have a difficult name to read.
Blade view
#foreach($races as $race)
<tr>
<td>{{ $race->display_dates }}</td>
<td>{{ $race->display_name }}</td>
<td>{{ $race->type->name }}</td>
<td>{{ $race->display_price }}</td>
<td>{{ $race->display_places }}</td>
<td>{{ $race->online_registration_is_open ? 'Yes' : 'No' }}</td>
</tr>
#endforeach
Controller
public function show(Group $group)
{
$races = $group->races;
$races->loadMissing('type'); // Eager loading
return view('races', compact('races'));
}
Model
// Accessors
public function getOnlineRegistrationIsOpenAttribute()
{
if (!$this->online_registration_ends_at && !$this->online_registration_starts_at) return false;
if ($this->online_registration_ends_at < now()) return false;
if ($this->online_registration_starts_at > now()) return false;
return true;
}
public function getNumberOfParticipantsAttribute()
{
return $this->in_team === true
? $this->teams()->count()
: $this->participants()->count();
}
// Accessors mainly used for displaying purpose
public function getDisplayPlacesAttribute()
{
if ($this->online_registration_ends_at < now()) {
return "Closed registration";
}
if ($this->online_registration_starts_at > now()) {
return "Opening date: " . $this->online_registration_starts_at;
}
return "$this->number_of_participants / $this->max_participants";
}
public function getDisplayPriceAttribute()
{
$text = $this->online_registration_price / 100;
$text .= " €";
return $text;
}
public function getDisplayDatesAttribute()
{
$text = $this->starts_at->toDateString();
if ($this->ends_at) { $text .= " - " . $this->ends_at->toDateString(); }
return $text;
}
public function getDisplayNameAttribute()
{
$text = $this->name;
if ($this->length) { $text .= " $this->length m"; }
if ($this->elevation) { $text .= " ($this->elevation m)"; }
return $text;
}
This code is working but I think it has a lot of cons : readability, mistake are possible, for example if the associated DB table has a name column while I am creating a getDisplayNameAttribute accessor here.
And that's only a start, I think I will need 30-40 more accessors for the other views...
Also, I will need to use some of them many time, for example getDisplayNameAttribute could be used in a regular page and an admin page (and maybe more).
I also looked at JsonResource and ViewComposer but JsonResource seems to be for APIs while ViewComposer seems to be especially for Views.
I also thought about prefixing the accessors with something like acc_ to reduce mistakes with existing db column :
public function getAccDisplayNameAttribute() { ... };
But I really don't think that's a solution, I'm not even sure if what I am doing is right or wrong. I also searched for best practices over the internet, without success.
You can create a class that specifically handle your transformations. Then you can load an instance of this class in the Model constructor.
You can also make your accessors classes use PHP magic getters if you wish.
Accessors Class
class RaceAccessors {
// The related model instance
private $model;
// Already generated results to avoid some recalculations
private $attributes = [];
function __construct($model)
{
$this->model = $model;
}
// Magic getters
public function __get($key)
{
// If already called once, just return the result
if (array_key_exists($key, $this->attributes)) {
return $this->attributes[$key];
}
// Otherwise, if a method with this attribute name exists
// Execute it and store the result in the attributes array
if (method_exists(self::class, $key)) {
$this->attributes[$key] = $this->$key($value);
}
// Then return the attribute value
return $this->attributes[$key];
}
// An example of accessor
public function price()
{
$text = $this->model->online_registration_price / 100;
$text .= " €";
return $text;
}
}
Model Class
class Race {
// The Accessors class instance
public $accessors;
function __construct()
{
$this->accessors = new \App\Accessors\RaceAccessors($this);
}
}
View
#foreach($races as $race)
<tr>
<td>{{ $race->accessors->price }}</td>
[...]
</tr>
#endforeach
I did not set a type for the $model variable here because you could use the same accessors class for other models that need to transform some fields in the same way than this accessors class does.
For example, the price accessor can work for a Race (in your case) but it could also work for other models, that way you can imagine creating groups of accessors used by many models without changing too much code to handle that.
I have 2 solutions for this case which ive tested on my local machine. Though am not sure if they comply with the best practices or design principles, that has to be seen, but i am sure that they will organize the code in a managable way.
First is you can create an accessor that is an array and club all your logic inside it which will compute and return the array. This can work for 4-5 attributes maybe, and you will have to make changes to your views to access the array instead of the properties. Code example 1 is given below
Second way is to create a separate class which will house all the different computing logic as methods. Lets say you make a ModelAccessor class, you can then create accessors in your Model class just the way you are doing now and return ModelAccessor->someMethod from inside each of them. This will add some neatness to your Model class and you can manage your computation logic from the class methods easily. Example code 2 below may make it more clear
Example 1 for arrays as accessors
Lets call the returned attribute $stats
public function getStatsAttribute(){
$stats = [];
if (!$this->online_registration_ends_at && !$this->online_registration_starts_at) $o=false;
if ($this->online_registration_ends_at < now()) $o = false;
if ($this->online_registration_starts_at > now()) $o = false;
$o= true;
$stats['online_registration_is_open'] = $o;
$stats['number_of_participants'] = $this->in_team === true
? $this->teams()->count()
: $this->participants()->count();
return $stats;
}
You will have to change your view files to use the stats array
<td>{{ $race->stats['number_of_participants'] }} </td>
<td>{{ $race->stats["online_registration_is_open"] ? 'Yes' : 'No' }}</td>
This might become messy for large num of attributes. To avoid that, you can have multiple arrays grouping similar things($stats, $payment_details, $race_registration, etc) or you can use a separate class to manage all of them, as in the next example
Example 2, Separate class with methods to set different attributes
class ModelAccessors
{
protected $race;
function __construct(\App\Race $race){
$this->race = $race;
}
public function displayPrice()
{
$text = $race->online_registration_price / 100;
$text .= " €";
return $text;
}
Then inside the model
public function getDisplayPriceAttribute()
{
$m = new ModelAccessor($this);
return $m->displayPrice();
}
Using this you wont have to update your blade files
3.
In case you have 30-40 accessors then i think maintaining a separate class with all the methods will be much simpler. In addition to that you can create the array of attributes from the class itself and call it like this,
class ModelAccessors
{
protected $race;
protected $attributes;
function __construct(\App\Race $race){
$this->race = $race;
$this->attributes = [];
}
public function displayPrice()
{
$text = $race->online_registration_price / 100;
$text .= " €";
$this->attributes['display_price'] = $text;
}
public function allStats(){
$this->displayPrice();
$this->someOtherMethod();
$this->yetAnotherMethod();
// ..
// you can further abstract calling of all the methods either from
// other method or any other way
return $this->attributes;
}
// From the model class
public function getStatsAccessor()
{
$m = new ModelAccessor($this);
// Compute the different values, add them to an array and return it as
// $stats[
// "display_price"= "234 €",
// "number_of_participants" = 300;
// ]
return $m->allStats()
}
Accessors and mutators are regular getter and setter functions in Laravel. It's just a more "elegant" way to saying it.
I could say, that getter and setter functions are to give you better control over your variables in your class, but it wouldn't help much understanding it a bit.
So what is it for? Why to use it?
We are already familiar with the DRY principle(Don't repeat yourself), which states that duplication is a logic, which should be eliminated. Or in kitchen language; don't write the same code twice, because it takes your time for no reason.
So how is this helps?
For example you have a spellchecker class, which is using an another app on the server - for example hunspell. For small strings you can use it directly and when the request comes, then you process the request, by calling a command with the shell_exec, then you get the result and return with it - everyone is happy.
However when the text is too long, then the php will exceed the 30 seconds(default) limit, and you wont get any results. In that case you can create a script, which is executed by a daemon process, so the 30 seconds limit wont be a problem. When the request arrives, you are passing the text to the daemon process using a unix socket for example, then the background process is doing the spellchecking for you and sends back the result.
In both cases you have to set certain variables for the spellchecker class, which might needs validation or formatting. And you don't want to develop it twice, because it takes time, makes mess, and increases the possible places where you can mess up something.
There are cases, when there are much more then just 2 places where you need to use preparations to set or get the data, and even only on 2 places it can help a lot to avoid code duplication. This can also help preventing the increase of your code complexity.
This is not all!
Sometimes you write classes, and god knows who will use it. Maybe it's for a public package. Then when the developer gets the package, he don't wanna care about how the email will be validated. He don't wanna know why the \n should be removed from the end of the string for specific variables when before he gets the variable from the file. He don't want to care about the implementation part of the integer validation. He just want to use the package, to get the job done as soon as possible.
What you are doing with setters and getters is not other then protecting your classes variables by controlling them through these methods.
Sometimes you want to hide your variables in your class, so other classes wont depend on them. It gives you the flexibility to change the implementation and the variable type.
Should you always use them?
No. Function calls are slow in php. Just check how slow the Laravel comparing to a native PHP code. Sometimes writing the getters and setters are totally meaningless, and just waste of time.
When should I use it then?
Use it when you need to restrict access to your variables. Making getters and setters for each field is overkill. It really depends on the situation, so use it only where it needs to be used.
More on this topic
Getter and Setter?
Why use getters and setters/accessors?
https://dev.to/scottshipp/avoid-getters-and-setters-whenever-possible-c8m
I have this controller for a RESTful API I am building in Laravel Lumen which takes a relatively big amount of parameters and parses them into where queries, and data is fetched depending on if they were provided. For example,
GET /nodes?region=California
GET /nodes?ip=127.0.0.1
I am currently taking them in the constructor, building an array of the parameters (since I couldn't figure out how to get the raw get array in Lumen and it would be inconvenient because I already have other parameters there), and filtering out the null values (I am setting values to null if they are not in the query).
Now, when it comes to filtering the values each in the array, I am doing it by a foreach array. This is the cleanest way I could figure out to do it, without too much code (I don't want to make my controllers too fat.).
Is there any other way to do this cleanly, maybe with separation of functions/classes?
Here is my constructor code:
/**
* Get some values before using functions.
*
* #param Request $request Instance of request.
*/
public function __construct(Request $request)
{
$this->offset = (int) $request->input('offset', 0);
// TODO: I'm not sure how to implement this, code in question
$this->filters = [
'region' => $request->input('region', null),
'name' => $request->input('name', null),
'ip' => $request->input('ip', null)
];
$this->filters = array_filter($this->filters, function ($v) {
return !is_null($v);
});
// Set a sane SQL limit.
$this->limit = 5;
$this->request = $request;
}
And the controller code:
/**
* List all nodes.
*
* #return [string] [JSON containing list of nodes, if sorted.]
*/
public function all()
{
try {
// use filters provided
$data = Nodes::limit($this->limit)->offset($this->offset);
foreach ($this->filters as $filter => $value) {
$data->where($filter, $value);
}
$data = $data->get();
$response = $this->respond($data);
} catch (\Exception $e) {
$response = $this->respondServerError('Could not retrieve data from database.');
}
return $response;
}
So any time I have to do filtering of a resource-list in an API, here's how I do it.
First off though, before I begin, a quick tip concerning getting the Request object when you're in your controller method: If you add Request $request as a parameter for your all() function, you will have access to the $request variable there, same as your constructor. So the complete signature would be public function all(Request $request). Controller methods have the same magic dependency injection that other class constructors get in Laravel/Lumen. Alternatively, in your function you can always ask the app() function to give you an object of a specific class. Because the Request object is bound in the Container to just 'request', you can ask for the full class name, or just 'request': $request = app('request');
So once I have my request object, inside my controller method I like to go through each filter either as a group, or one-by-one, depending on how complex each filter is. Sometimes filters are complex, like a list of comma-separated IDs that need to be exploded into an array. If it's just simple string filters though, I tend to throw the list into an array and run through that.
Here's an example function to illustrate some ideas:
public function getIndex(Request $request)
{
//Create a User object to append WHERE clauses onto
$user = app('App\Models\User');
//Run through our simple text fields
foreach(['first_name', 'last_name', 'region', 'ip'] as $field) {
if ($request->has($field)) {
$user->where($field, $request->input($field));
}
}
//This field uses a LIKE match, handle it separately
if ($request->has('email')) {
$user->where('email', LIKE, '%' . $request->input('email') . '%');
}
//This field is a list of IDs
if ($request->has('id')) {
$ids = explode(',', $request->input('id'));
$user->whereIn('id', $ids);
}
//Use pagination
$users = $user->paginate(25);
/**
* Continue with the rest of response formatting below here
*/
}
You'll notice I used the paginate function to limit my results. When building an API endpoint that lists resources, you're going to want to put in your headers (my preference) or the response body information on how to get the first, previous, next, and last page of results. The Pagination feature in Laravel makes that easy, as it can construct most of the links using the links() method.
Unfortunately, you need to tell it what filter parameters were passed in the request so it can make sure it adds those to the links it generates. Otherwise you'll get links back without your filters, which doesn't do the client very much good for paging.
So here's a more complete example of recording filter parameters so they can be appended onto pagination links:
public function getIndex(Request $request)
{
//Create a User object to append WHERE clauses onto
$user = app('App\Models\User');
//List of filters we found to append to links later
$appends = [];
//Run through our simple text fields
foreach(['first_name', 'last_name', 'region', 'ip'] as $field) {
if ($request->has($field)) {
$appends[$field] = $request->input($field);
$user->where($field, $request->input($field));
}
}
//This field uses a LIKE match, handle it separately
if ($request->has('email')) {
$appends['email'] = $request->input('email');
$user->where('email', LIKE, '%' . $request->input('email') . '%');
}
//This field is a list of IDs
if ($request->has('id')) {
$appends['id'] = $request->input('id');
$ids = explode(',', $request->input('id'));
$user->whereIn('id', $ids);
}
//Use pagination
$users = $user->paginate(25);
//Make sure we append our filter parameters onto the pagination object
$users->appends($appends);
//Now calling $users->links() will return the correct links with the right filter info
/**
* Continue with the rest of response formatting below here
*/
}
Pagination documentation can be found here: https://laravel.com/docs/5.2/pagination
For an example of how pagination linking can be awesomely done, check out Github's API documentation: https://developer.github.com/v3/#pagination
In the end it's not too far off from what you were doing, conceptually. The advantage here is that you move the code into the method that needs it, instead of having it run in your constructor every single time the controller is initialized, even if a different method will be called.
Hope that helps!
I have the following example in which I tend to use a couple of classes, to create a simple web app.
The file hierarchy seems like this.
> cupid
- libs
- request
- router
- database
- view
- bootstrap.php
- index.php
The index.php just calls the bootstrap.php which in turn contains something like this:
// bootstrap.php
namespace cupid
use request, router, database, view;
spl_autoload_register(function($class){ /* autoload */ });
$request = new view;
$response = new response;
$router = new router;
$database = new database;
$router->get('/blog/{id}', function($id) use ($database, $view) {
$article = $database->select("SELECT blog, content FROM foo WHERE id = ?",[$id]);
$view->layout('blogPage', ['article'=>$article]);
});
As you can probably tell, my problem is this line:
$article = $database->select("SELECT blog, content FROM foo WHERE id = ?", [$id]);
Which I don't want to use, and instead try a " Domain Object Model " approach.
Now, given that I will add another folder called domain, with blog.php
> cupid
- domain
- Blog.php
- libs
...
And fill blog.php with properties mapping table rows, and getter and setters ..
namespace App\Domain;
class Blog {
private $id, $title, $content, $author;
public function getTitle(){
return $this->title;
}
public function setTitle($title){
$this->title = $title;
}
...
}
My question is: Assuming my understanding of DOM is so far correct, and that I have a CRUD/ORM class, or a PDO wrapper to query the database;
"How can I tie together, i.e. the blog model with the PDO wrapper to fetch a blog inside my bootstrap file?"..
As far as a Domain Object you basically already have written one, your blog object. To qualify as a domain model all a class must to is to provide a representation along with any of the functionality of a concept within your problem space.
The more interesting problem here and the one you appear to be struggling with is how to persist a domain model. Keeping with the tenet of the single responsibility principle your Blog class should deal with being a blog post and doing the things that a blog post can do, not storing one. For that you would introduce the concept of a repository of blog posts that would deal with storing and retrieving objects of this type. Below is a simple implementation of how this can be done.
class BlogRepository {
public function __construct(\cupid\database $db){
$this->db = $db;
}
public function findById($id){
$blogData = $this->db->select("select * from blog where id = ?", [$id]);
if ($blogData){
return $this->createBlogFromArray($blogData);
}
return null;
}
public function findAllByTag($tag){...}
public function save(Blog $blog) {...}
private function createBlogFromArray(array $array){
$blog = new Blog();
$blog->setId($blogData["id"]);
$blog->setTitle($blogData["title"]);
$blog->setContent($blogData["content"]);
$blog->setAuthor($blogData["author"]);
return $blog;
}
}
Then your controller should look something like this.
$router->get('/blog/{id}', function($id) use ($blogRepository, $view) {
$article = $blogRepository->findById($id);
if ($article) {
$view->layout('blogPage', ['article'=>$article]);
} else {
$view->setError("404");
}
});
To truly be SOLID the above class should be a database specific implementation of a BlogRepository interface to adhere to IoC. A factory should also probably be supplied to BlogRepository to actually create the blog objects from data retrieved from the store.
In my opinion one of the great benefits of doing this is you have a single place where you can implement and maintain all of your blog related interactions with the database.
Other Advantages to this method
Implementing caching for your domain objects would be trivial
Switching to a different data source (from flat files, blogger api, Document Database Server,PostgresSQL etc.) could be done easily.
You can alternatively use a type aware ORM for a more general solution to this same problem. Basically this Repository class is nothing more than a ORM for a single class.
The important thing here is that you are not talking directly to the database and leaving sql scattered throughout your code. This creates a maintenance nightmare and couples your code to the schema of your database.
Personally I always tend to stick the database operations in a database class which does all the heavy lifting of initialising the class, opening the connection etc. It also has generic query-wrappers to which I pass the SQL-statements which contains the normal placeholders for the bound variables, plus an array of the variables to be bound (or the variable number of parameters approach if thats suits you better). If you want to bind each param individually and not use the $stmt->execute(array()); You just pass in the types with the value in a data structure of your choosing, multi dim array, dictionary, JSON, whatever suits your needs and you find easy to work with.
The model class it self (Blog in your case) then subclasses the Database. Then you have a few choices to make. Do you want to use the constructor to create only new objects? Do you want it to only load based on IDs? Or a mix of both? Something like:
function __construct(id = null, title = null, ingress = null, body = null) {
if(id){
$row = $this->getRow("SELECT * FROM blog WHERE id = :id",id); // Get a single row from the result
$this->title = $row->title;
$this->ingress = $row->ingress;
$this->body = $row->body;
... etc
} else if(!empty(title,ingress,body)){
$this->title = title;
... etc
}
}
Maybe neither? You can skip the constructor and use the new(title, ingress, body), save() and a load(id) methods if thats your preference.
Of course, the query part can be generalised even further if you just configure some class members and let the Database-superclass do the query building based on what you send in or set as member-variables. For example:
class Database {
$columns = []; // Array for storing the column names, could also be a dictionary that also stores the values
$idcolumn = "id"; // Generic id column name typically used, can be overridden in subclass
...
// Function for loading the object in a generic way based on configured data
function load($id){
if(!$this->db) $this->connect(); // Make sure we are connected
$query = "SELECT "; // Init the query to base string
foreach($this->columns as $column){
if($query !== "SELECT ") $query .= ", "; // See if we need comma before column name
$query .= $column; // Add column name to query
}
$query .= " FROM " . $this->tablename . " WHERE " . $this->idcolumn . " = :" . $this->idcolumn . ";";
$arg = ["col"=>$this->idcolumn,"value"=>$id,"type"=>PDO::PARAM_INT];
$row = $this->getRow($query,[$arg]); // Do the query and get the row pass in the type of the variable along with the variable, in this case an integer based ID
foreach($row as $column => $value){
$this->$column = $value; // Assign the values from $row to $this
}
}
...
function getRow($query,$args){
$statement = $this->query($query,$args); // Use the main generic query to return the result as a PDOStatement
$result = $statement->fetch(); // Get the first row
return $result;
}
...
function query($query,$args){
...
$stmt = $this->db->prepare($query);
foreach($args as $arg){
$stmt->bindParam(":".$arg["col"],$arg["value"],$arg["type"]);
}
$stmt->execute();
return $stmt;
}
...
}
Now as you see the load($id), getrow($query,$args) and query($query,$args) is completely generic. ´getrow()´is just a wrapper on query() that gets the first row, you may want to have several different wrappers that to or interpret your statement result in different ways. You may also even want to add object specific wrappers to your models if they cannot be made generic. Now the model, in your case Blog could look like:
class Blog extends Database {
$title;
$ingress;
$body;
...
function __construct($id = null){
$this->columns = ["title","ingress","body","id",...];
$this->idcolumn = "articleid"; // override parent id name
...
if($id) $this->load($id);
}
...
}
Use it as so: $blog = new Blog(123); to load a specific blog, or $blog = new Blog(); $blog->title = "title"; ... $blog->save(); if you want a new.
"How can I tie together, i.e. the blog model with the PDO wrapper to fetch a blog inside my bootstrap file?"..
To tie the two together, you could use an object-relational mapper (ORM). ORM libraries are built just for glueing your PHP classes to database rows. There are a couple of ORM libraries for PHP around. Also, most ORMs have a built in database abstraction layer, which means that you can simply switch the database vendor without any hassle.
Considerations when using an ORM:
While introducing a ORM also introduces some bloat (and some learning), it may not be worthwhile investing the time for simply a single Blog object. Although, if your blog entries also have an author, one or multiple categories and/or associated files, an ORM may soon help you reading/writing the database. Judging from your posted code, an ORM will pay off when extending the application in the future.
Update: Example using Doctrine 2
You may have a look at the querying section of the official Doctrine documentation to see the different options you have for read access. Reconsider the example you gave:
// current implementation
$article = $database->select("SELECT blog, content FROM foo WHERE id = ?",[$id]);
// possible implementation using Doctrine
$article = $em->getRepository(Blog::class)->find($id);
However, ideally you define your own repository to separate your business logic from Doctrines API like the following example illustrates:
use Doctrine\ORM\EntityRepository;
interface BlogRepositoryInterface {
public function findById($id);
public function findByAuthor($author);
}
class BlogRepsitory implements BlogRepositoryInterface {
/** #var EntityRepository */
private $repo;
public function __construct(EntityRepository $repo) {
$this->repo = $repo;
}
public function findById($id) {
return $this->repo->find($id);
}
public function findByAuthor($author) {
return $this->repo->findBy(['author' => $author]);
}
}
I hope the example illustrates how easily you can separate your business domain models and logic from the underlying library and how powerful ORMs can come into play.
i need differents results from a model but i don't understand if it is correct make a single call and leave to model all the work or make more calls and collect the result to pass to the view when tables aren't joined or when i need fetch one row from a table and differents rows from others.
First example (more calls, collect and send to view):
CONTROLLER
// call functions of model
$modelName = new Application_Model_DbTable_ModelName();
$rs1 = $modelName->getTest($var);
$rs2 = $modelName->getTest2($var2);
// collect data
$pippo = $rs1->pippo;
if ($rs2->pluto == 'test') {
$pluto = 'ok';
} else {
$pluto = 'ko';
}
// send to view
$this->view->pippo = $pippo;
$this->view->pluto = $pluto;
MODEL
public function getTest($var) {
...
select from db...
return $result;
...
}
public function getTest2($var) {
...
select from db...
return $result;
...
}
Second example (one call, model collect all data, return to controller and send to view):
CONTROLLER
// call one function of model
$modelName = new Application_Model_DbTable_ModelName();
$rs = $modelName->getTest($var);
MODEL
public function getTest($var) {
...
select from db...
if ($result > 0) {
call other function
call other function
collect data
return $result;
...
}
Thanks
There's no one correct answer to this question, but in general, you should endeavor to keep your business logic in one place. Think of it as, "thin controller, thick model." I.e., keep the controllers as small and simple as possible and put all the business logic in the models.
There seems to be a few questions here:
But if i don't need to interact with db and i need only a simply
function is better put that function in model? For example:
CONTROLLER:
public function printAction() {
$data = $this->getRequest()->getPost();
$label = "blablabla";
$this->view->label = $label;
}
first, in the context of Zend Framework this particular example doesn't make much sense. The whole point of the controller is to populate the view template. However, I do get the idea. I would point you to Action Helpers and View helpers as a means to address your concerns. You can always add a utility class to your library for those pieces of code that don't seem to fit anywhere else.
Action Helpers typically are employed to encapsulate controller code that may be repetitive or reusable. They can be as simple or as complex as required, here is a simple example:
class Controller_Action_Helper_Login extends Zend_Controller_Action_Helper_Abstract
{
/**
* #return \Application_Form_Login
*/
public function direct()
{
$form = new Application_Form_Login();
$form->setAction('/index/login');
return $form;
}
}
//add the helper path to the stack in the application.ini
resources.frontController.actionhelperpaths.Controller_Action_Helper = APPLICATION_PATH "/../library/Controller/Action/Helper"
//the helper is called in the controller
$this->_helper->login();
a View helper does the same thing for the view templates:
class Zend_View_Helper_PadId extends Zend_View_Helper_Abstract
{
/**
* add leading zeros to value
* #param type $id
* #return string
*/
public function padId($id)
{
return str_pad($id, 5, 0, STR_PAD_LEFT);
}
}
//in this example the helper path is added to the stack from the boostrap.php
protected function _initView()
{
//Initialize view
$view = new Zend_View();
//add custom view helper path
$view->addHelperPath('/../library/View/Helper');
//truncated for brevity
$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
'ViewRenderer');
$viewRenderer->setView($view);
//Return it, so that it can be stored by the bootstrap
return $view;
}
//and to use the helper in the view template
//any.phtml
<?php echo $this->padId($this->id) ?>
i need differents results from a model but i don't understand if it is
correct make a single call and leave to model all the work or make
more calls and collect the result to pass to the view when tables
aren't joined or when i need fetch one row from a table and differents
rows from others.
This question is more about structure then about correctness.
You can interact with your database table models in Action and View helpers for simple/repetitive queries if you need to, however most developers might frown on this approach as being difficult to maintain or just ugly.
Many people seem to favor Doctrine or Propel to help them manage their database needs.
At this point I like to roll my own and currently favor domain models and data mappers, not an end all be all pattern, but seems to be appropriate to your question.
This is not a simple suggestion to implement for the first time, however i found two articles helpful to get started:
http://phpmaster.com/building-a-domain-model/
http://phpmaster.com/integrating-the-data-mappers/
and if you really want to get into it try:
http://survivethedeepend.com/
I hope this answers at least a part of your questions.
I'm writing my first application with Zendframework.
My question is about the Model–View–Controller (MVC) architectural pattern.
I currently have a model with refer to a database table.
Here's the classes that I currently have :
Model_Person
Model_PersonMapper
Model_DbTable_Person
Now, I see a lot of examples on the net, but all of them are simple cases of insert/update/delete.
In my situation, I have to check if a person exists, and if it doesn't, I have to insert it and retrieve the ID (I know save return the Id, but it's not exactly what I have to do, this is and example).
It's quit simple, but I want to know where to put the database logic for all the others specific cases. Some others cases might involve checks across other tables or ... whatever !
Should I add all the specific functions in my Model_XXXXMapper with something that would be very specific with the current validation/process that I want to do? like a function getIdOfThePersonByNameOrInsertIfNotExists() (sample name of course!!!)
Or should it reside in the controller with some less specifics access to my model would be validated?
In other word, where do I put all the data specifics functions or check ?
I think the real work should occur in your model objects, not in the controller. Any selects/creates that start with the person table would be in the DbTable_Person object, things like:
// DbTable_Person
// returns sets of or single Person objects
public function createByName( $name ) // perhaps throws exception if name already exists
public function findById( $id )
public function findByName( $name )
public function findHavingAccount( $account_id ) // references another table
// controller
// with your example, like what Galen said,
// I would let the controller handle this logic
$person = $person_table->findByName($name);
if ( !$person ) {
$person = $person_table->createByName($name);
}
if ( !$person ) { throw new Zend_Exception('huh?'); }
$id = $person->id; // you wanted the ID
I would definitely split the function up into search/create functions.
Here's a basic implementation...
$personTG = new Model_PersonTableGateway;
if ( !$person = $personTG->findByName( $name ) ) {
$person = new Model_Person;
$person->name = $name;
// other variables
$newPersonId = $personTG->create( $person ); // creates a new person
}
I use table gateway. You can substitute your class for the TG.
You can have the create() function return just the id of the newly created person, or the entire person...it's up to you.
You might be interested in Zend_Validate_Db_NoRecordExists and its sister. If you are using Zend_Form you can add this validator to your form element. Many folks use Zend_Form to validate and filter data before they reach the domain model.
If you are not using Zend_Form, you can simply use this validation class in your service layer. A simple service class could be something like
`
class Service_Person_Validate
{
public function creatable($data)
{ // return true|false
}
}