What is the difference between caching an Eloquent model with
$myResult = Model::remember(5)->get();
and using the Cache itself:
$myResult = Cache::remember('myModel', 5, function(){
return Model::get();
});
Are they identical, or is each used for a different purpose?
You are caching the same thing - but in two different ways. They are technically identical (the same query result is cached in both examples for 5 minutes) - but they are different from a "separation of concerns" issue.
When you deal with a Model - perhaps it is in your controller - your Controller should have no real knowledge of the 'inner' workings of the Model. It should just ask for information, and be given the right information.
So using your examples - we have two ways a controller could be built:
Firstly - we could let the controller know "too much" and do this:
function showUser($id)
{
$myResult = Cache::remember('myModel', 5, function(){
return Model::find($id);
});
}
In this example - the controller knows the inner works of the model, and it is also dictating how long the cache should be. But the controller should have no knowledge of what a user is, or how long it should be cached for - that is best left up to the Model to manage. i.e. what happens if you look for a user elsewhere in your code - you have to duplicate you caching.
Meanwhile you could do it this way instead:
function showUser($id)
{
$user= User::getUser($id);
}
and then in your User Model
function getUser($id)
{
return User::remember(5)->find($id);
}
This way the management of the user remains inside the model. The model knows how long (if at all) a user should be cached for. (Yes - some people will say caching should be abstracted from the model into a repository - but lets keep it simple for now). In this example the controller has no idea that the result is cached - but it doesnt need to nor should it - all it needs is the user with an id of $id
Related
In some model classes I want to implement cache. And I wanted to do that like:
UsersModel::model()->findByAttributes([...])
In that class I wanted to override method beforeFind() to send request first to cache server, but it seems that method does not take any additional parameters, nor does have object with attributes.
Putting additional conditions/checks in top level code something like :
$response = Yii::app()->cache->get('userUserLogin');
if(empty($response) == true) {
//fetch data from db and set to cache
$userModel = UsersModel::model->findByAttributes([...])
Yii::app()->cache->set('user' . $userModel->username, $userModel->getAttributes());
}
is not nice and trivial, leading to many branches.
You should not use beforeFind() for that. Besides technical problems in implementation, you may get many side effects and hard to debug bugs because of that. That is because cache may be out of date and many internal Yii logic may rely on assumption, that findByAttributes() (and other methods) always fetches fresh data from database. You will also not be able to ignore cache and get model directly from database.
In general you have 2 options:
1. Use CActiveRecord::cache()
$model = UsersModel::model()->cache(60)->findByAttributes([...])
This will query cache results for 60 seconds.
2. Custom helpers
You may add custom methods, which will simplify using cached active records:
public static function findByAttributesFromCache($attributes = []) {
$result = Yii::app()->cache->get(json_encode($attributes));
if ($result === false) {
//fetch data from db and set to cache
$result = static::model()->findByAttributes($attributes);
Yii::app()->cache->set(json_encode($attributes), $result, 60);
}
return $result;
}
You can add such method to trait and reuse it in multiple models. Then all you need is:
$userModel = UsersModel::findByAttributesFromCache([...]);
I have the model:
class Task extends Model {
}
with some fields
protected $fillable = ['message', 'due_time', 'status', 'etc...'];
I've added custom function:
public function getEditableStateFor{AttributeName}
In my helper function I check that if
method_exists($class, 'getEditableStateForField1')
than I allow to edit this field depending on boolean value returned from this function.
Example:
if( ! $class->getEditableStateForField1() ) {
return "You can not edit field field1";
}
Here is how looks like some functions in Task:
private function isCreator() {
$user = Auth::user();
if($user) {
return $user->id === $this->creator_id;
}
return false;
}
public function getEditableStateForMessage() {
return $this->isCreator();
}
public function getEditableStateForDueTime() {
return $this->isCreator();
}
Is this a good way to do it or it is very bad design because of hidden dependency on Auth::user()?
What is a better way?
I do not want to put this logic inside controllers because this logic propagates to another models and is universal across application.
I'm like you and like to have Models that contain as much of the business logic as possible while remaining totally free of depencies on the "web" part of the application, which I believe should stay in Controllers, Request objects, etc. Ideally, Models should be easily usable from command line interfaces to the application, from within the Tinker REPL, and elsewhere while still guaranteeing data integrity and that business rules are observed.
That said, it seems the Laravel creators had slightly different ideas, hence the Auth facade being easily available in the model.
What I would likely do is add a parameter of type User to the getEditableStateFor series functions, and then in turn pass that parameter to isCreator ($user) and elsewhere. That also frees you up to be able to allow associated users to edit each other's Tasks if that ever became a desired feature in the future.
Edit: another, perhaps better or perhaps worse, is to have an instance method like setCurrentUser ($user) then use setFieldNameAttribute methods so that the controller doesn't have to check the editability of fields, keeping that the model's responsibility. Then you could call the getEditableStateFor methods, which now check for the current user set by the above method (maybe falling back to Auth::user() or throwing a helpful error), inside the setter.
So let's say i have a controller named pages.
with this function.
function __construct(){
parent::__construct();
$this->auth['result'] = $this->is_logged_in();
}
The above code, calls for the extended function 'MY_Controller' which has a function called is_logged_in() and will simply fetch session data.
My first question would be, am i doing this authentication correct? or am i simply under-using it?
Anyways, my main question is regarding loading views.
Say i have many views, some views are accessed only by TYPE A user that has an access level different to that of TYPE B user.
Say both accounts are created differently, like in some online websites where there are users and shopowners that registered to advertise their shop irl, or for example, online travel. One account is user(normal/travelers) and one account is hotelowner(owns a hotel irl). So both will have overlapping views and some dedicated views for them.
How do i go about it when loading views?
Say in my controller i got functions,
public function view_home(){
}
public function view_userprofile(){
}
public function vieW_hotelprofile(){
}
You get my idea from above, i have separate functions for loading different views. But my question is that, how do i go about making it into one big function?
like for example,
public function view($page ='home' ){
}
the view function would be like a terminal for different accounts to access the views.
This would be simpler if i don't have to check access_levels. My first idea is to make a very long IF(){}ELSEIF(){} statement for example.
if($this->auth['result'] = 1 ){
load view here.
}
is this acceptable? also, how would i go about it efficiently? and regarding the authentication, this part i'm really confused if i'm using it the right way or not.
Also if i ever do a very long if statement, what values would i check? like i know you have to check the access level for certain views but do i also check the username logged in and match it to the database if it's accesslevel is the same?
You can try using switch case
public function loadView()
{
switch($this->auth['result']){
case 1:
$view= "home";
break;
case 2:
$view= "userprofile";
break;
case 3:
$view= "hotelprofile";
break;
default:
$view= "Welcome";
break;
}
$this->load->view($view);
}
After reading a lot of posts and Stack Overflow resources, I've still got some problems about the famous question about "where to put business logic?" Reading StackOverflow Question and A Blog Post, I believe I've understood the issue of code separation well.
Suppose I have a web form where you can add a user that will be added to a db. This example involves these concepts:
Form
Controller
Entity
Service
Repository
If I didn't miss something, you have to create an entity with some properties, getters, setters and so on in order to make it persist into a db. If you want to fetch or write that entity, you'll use entityManager and, for "non-canonical" query, entityRepository (that is where you can fit your "query language" query).
Now you have to define a service (that is a PHP class with a "lazy" instance) for all business logic; this is the place to put "heavy" code. Once you've recorded the service into your application, you can use it almost everywhere and that involves code reuse and so on.
When you render and post a form, you bind it with your entity (and with constraints of course) and use all the concepts defined above to put all together.
So, "old-me" would write a controller's action in this way:
public function indexAction(Request $request)
{
$modified = False;
if($request->getMethod() == 'POST'){ // submit, so have to modify data
$em = $this->getDoctrine()->getEntityManager();
$parameters = $request->request->get('User'); //form retriving
$id = $parameters['id'];
$user = $em->getRepository('SestanteUserBundle:User')->find($id);
$form = $this->createForm(new UserType(), $user);
$form->bindRequest($request);
$em->flush();
$modified = True;
}
$users = $this->getDoctrine()->getEntityManager()->getRepository('SestanteUserBundle:User')->findAll();
return $this->render('SestanteUserBundle:Default:index.html.twig',array('users'=>$users));
}
"New-me" has refactored code in this way:
public function indexAction(Request $request)
{
$um = $this->get('user_manager');
$modified = False;
if($request->getMethod() == 'POST'){ // submit, so have to modify data
$user = $um->getUserById($request,False);
$form = $this->createForm(new UserType(), $user);
$form->bindRequest($request);
$um->flushAll();
$modified = True;
}
$users = $um->showAllUser();
return $this->render('SestanteUserBundle:Default:index.html.twig',array('users'=>$users));
}
Where $um is a custom service where all code that you can't see from #1 code piece to #2 code piece is stored.
So, here are my questions:
Did I, finally, get the essence of symfony2 and {M}VC in general?
Is the refactor a good one? If not, what would be a better way?
Post Scriptum: I know that I can use the FOSUserBundle for User store and authentication, but this is a basic example for teach myself how to work with Symfony.
Moreover, my service was injected with ORM.Doctrine.* in order to work (just a note for who read this question with my same confusion)
There are two main approaches regarding on where to put the business logic: the SOA architecture and the domain-driven architecture. If your business objects (entities) are anemic, I mean, if they don’t have business logic, just getters and setters, then you will prefer SOA. However, if you build the business logic inside your business objects, then you will prefer the other. Adam Bien discusses these approaches:
Domain-driven design with Java EE 6: http://www.javaworld.com/javaworld/jw-05-2009/jw-05-domain-driven-design.html
Lean service architectures with Java EE 6: http://www.javaworld.com/javaworld/jw-04-2009/jw-04-lean-soa-with-javaee6.html
It’s Java, but you can get the idea.
Is the refactor a good one? If not, what would be a better way?
One of the best framework practices is using param converters to directly invoke an entity from user request.
Example from Symfony documentation:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* #Route("/blog/{id}")
* #ParamConverter("post", class="SensioBlogBundle:Post")
*/
public function showAction(Post $post)
{
}
More on param converters:
http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
Robert C. Martin (the clean code guy) says in his new book clean architecture, that you should put your business logic independently from your framerwork, because the framework will change with time.
So you can put your business logic in a seperate folder like App/Core or App/Manager and avoid inheretence from symfony classes here:
<?php
namespace App\Core;
class UserManager extends BaseManager implements ManagerInterface
{
}
I realize this is an old question, but since I had a similar problem I wanted to share my experience, hoping that it might be of help for somebody.
My suggestion would be to introduce a command bus and start using the command pattern. The workflow is pretty much like this:
Controller receives request and translates it to a command (a form might be used to do that, and you might need some DTO to move data cleanly from one layer to the other)
Controller sends that command to the command bus
The command bus looks up a handler and handles the command
The controller can then generate the response based on what it needs.
Some code based on your example:
public function indexAction(Request $request)
{
$command = new CreateUser();
$form = $this->createForm(new CreateUserFormType(), $command);
$form->submit($request);
if ($form->isValid()) {
$this->get('command_bus')->handle();
}
return $this->render(
'SestanteUserBundle:Default:index.html.twig',
['users' => $this->get('user_manager')->showAllUser()]
);
}
Then your command handler (which is really part of the service layer) would be responsible of creating the user. This has several advantages:
Your controllers are much less likely to become bloated, because they have little to no logic
Your business logic is separated from the application (HTTP) logic
Your code becomes more testable
You can reuse the same command handler but with data coming from a different port (e.g. CLI)
There are also a couple downsides:
the number of classes you need in order to apply this pattern is higher and it usually scales linearly with the number of features your application exposes
there are more moving pieces and it's a bit harder to reason about, so the learning curve for a team might be a little steeper.
A couple command buses worth noting:
https://github.com/thephpleague/tactician
https://github.com/SimpleBus/MessageBus
I'm using the MVC PHP framework Codeigniter and I have a straight forward question about where to call redirect() from: Controller or Model?
Scenario:
A user navigates to www.example.com/item/555. In my Model I search the item database for an item with the ID of 555. If I find the item, I'll return the result to my controller. However, if an item is not found, I want to redirect the user somewhere. Should this call to redirect() come from inside the model or the controller? Why?
No your model should return false and you should check in your controller like so:
class SampleModel extends Model
{
//Construct
public function FetchItem($id)
{
$result = $this->db->select("*")->from("table")->where("item_id",$id)->get();
if($result->num_rows() == 0)
{
return false;
}
//return result
}
}
and within your controller do:
function item($id)
{
$Item = $this->SampleModel->FetchItem($id);
if(!$Item)
{
redirect("class/error/no_item");
}
}
Models are for data only either return a standard result such as an key/value object or a boolean.
all logic should be handled / controlled by the Controller.
Models are not page specific, and are used globally throughout the whole application, so if another class / method uses the model, it might get redirect to the incorrect location as its a different part of your site.
It seems like the controller would be the best place to invoke your redirect because the controller typically delegates calls to the model, view, or in your case, another controller.
However, you should use whatever makes the most sense for your application and for what will be easier to maintain in the future, but also consider that rules do exist for a reason.
In short, if a coworker were to try to fix a bug in your code, what would the "reasonable person" standard say? Where would most of them be most likely to look for your redirect?
Plus, you said you're returning the result to your controller already... perhaps that's where you should make your redirect...