I´m having problems understanding some basic strategies using doctrine 2 in codeIgniter 2 development.
Background: CI is up and running with doctrine, I can get entities from database and save them.
Example:
I have few controllers, where I would like to list latest articles. In my pure CI application I would have 'getLatest' -method in my model. I then would call this in all of my controllers, loading correct view etc.
But now I have doctrine models and not sure how to do this. I just cant add same method to my model. What I have done is moved that getLatest-logic to controller and this does not look right. Now I would need to call other controllers from my actions to get these latest articles. Or should I really duplicate that code in every controller where I need it?
I am still struggling with this. My CI models and doctrine entities have same names and are located in same "application/models/" folder. These together cause several problems. I am trying to change this path, but cant get it work. I have used this library class for loading doctrine: http://wildlyinaccurate.com/integrating-doctrine-2-with-codeigniter-2/ Any tips?
Your getLatest functionality belongs in the model, not the controller. Each of your Doctrine 2 models has its own Repository, which you can get with $em->getRepository('Your\Model'). The default repository (\Doctrine\ORM\EntityRepository) has several methods that might be useful to you:
$repository = $em->getRepository('Your\Model');
$all_entities = $repository->findAll();
$some_entities = $repository->findBy(array(
'some_column' => 'some_condition'
), $order_by, $limit, $offset);
If Doctrine's built-in repository doesn't do what you need, you can create your own repository for any of your models:
class YourRepository extends EntityRepository
{
public function getLatest()
{
// Your getLatest logic here
return $this->_em->createQuery('SELECT m FROM Your\Model LIMIT 10');
}
}
Instead of having a "Model", you can wrap Doctrine components in a Service and keep your controller as simple as possible. Using MVC framework or not, it is important to follow SOLID, especially the Single Responsibility Principle (SRP).
Consider the following simplified example:
class UserService extends AbstractService
{
private $em;
private $repository;
public function __construct(EntityManager $em, UserRepository $repository)
{
$this->em = $em;
$this->repository = $repository;
}
public function create($data)
{
// business logic here
$user = new User();
$user->setUsername($data['username']);
$user->setEmail($data['email']);
$em->persist($this->user);
$em->flush();
}
public function getLatest()
{
// additional business logic could be implemented
return $this->repository->getLatest();
}
}
Then I can call the service from the Controller or from any other service:
class UserController extends Controller
{
/**
* #var UserService $service
*/
private $service;
public function __construct(UserService $service)
{
$this->service = $service;
}
public function create()
{
// some form validation here
$data = $this->input->post('user');
$result = $this->service->create($data);
// do something with the result
}
}
No, you should not neither duplicate nor move that code to controllers. Haven't tried that personally, but I think that doctrine Entities and CI models are completely independent. You may introduce some CI's models that incorporate that getLatest method, but really operate on Doctrine entities.
So you'll just use Doctrine entities and CI's models as usual. But don't forget to load doctrine library (if you bootstrapped as told in Doctrine manual) into models that use Doctrine entities.
Related
I have two controllers (FirstController, SecondController) that use the same functions, to avoid rewriting them I thought of creating another controller (ThirdController) and subsequently calling the functions through it.
the problem is that if in ThirdController there are relationship query they give me the error that "they don't exist".
example:
User Model
class User extends Authenticatable implements AuthenticatableUserContract
{
use HasFactory, Notifiable;
public function comments(){
return $this->hasMany('App\Models\Comment');
}
ThirdController
class ThirdController extends Controller
{
public static function example($id){
$comments = Comment::find($id)->comments();
return $comments;
}
}
FirstController/SecondController
public function example2(Request $request){
return ThirdController::example($request->id);
When call the route it give me error:
BadMethodCallException: Method Illuminate\Database\Eloquent\Collection::comments does not exist.
my questions are:
Is there any better method instead of creating a third controller?
Is there a solution to this?
p.s. I know I could very well build the queries without exploiting the relationship, but where's the beauty of that? :D
You do not need to create 3rd controller. You can create a class and here you write the query in a function and use this class in controller1 and controller2 by dependency injection.
First thing that's not a best practice to define a static method in one controller and call it in another controller (not recommended way).
Second you're calling Comment::find($id) with comments() relation. you should call a User class, like below snippet:
class ThirdController extends Controller
{
public static function example($id){
$comments = User::find($id)->comments();
return $comments;
}
}
RECOMEND APPROACH:
Creat a one seperate service/repository class in which you'll define a common method i.e. getUserComments() and use it in both or all of three controllers (upto your requirement/needs). By this way you implementations will be on a centric place.
If you want learn about Repository pattern you can get basic idea from: Article#1
I know DI, Solid Principles, factory patterns, adapter pattern and many more maybe. Now let's say I am creating a laravel application and it's gonna be huge. Let's say I have a postscontroller which is resource and has CRUD methods. Now Let's say in that controller's functions, I have a post Model and use it to retrieve data from database. I have a store function where I am creating new Post() and then put it into database.
1) Is it a good practice to have Post model directly in PostController's function and use new Post() also? What is bad in it? I know that this way I am not using dependency injection and patterns, but still why is it bad? As you know , I can still mock the object without dependency injection since laravel has so many amazing testing features. Then why is it that bad to write new keyword in controller's functions and also use Post model directly?
To give you one answer to your question. There is this "Slim controllers, fat models" concept. I used to defer the object creation to the object itself by Named Constructers.
class UserController
{
public function create(UserCreateRequest $request)
{
$user = User::createFromRequest($request);
// do anything else
}
}
class User
{
public static function createFromRequest(UserCreateRequest $request)
{
$user = new User;
$user->first_name = $request->first_name;
// ...
$user->save();
return $user;
}
}
With this you can have more different constructors like User::createAdmin and its testable. You just need to mock the Request.
I'm trying to get a more concrete understanding of MVC and keeping the controller layer as thin as possible.
One thing I keep asking myself is "Where should I call modelname->save()?"
Looking at the Laravel documentation, they set data to the model and call save in the controller which doesn't seem right...
<?php
namespace App\Http\Controllers;
use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class FlightController extends Controller
{
public function store(Request $request)
{
// Validate the request...
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
}
}
This is quite a simple example and might be why they do it all in the controller.
From my understanding and everything I've been reading, all business logic should sit inside the model, the controller is responsible for "traffic control" between the view and the model.
So would I be calling save inside the model itself? or should I be using a service layer?
Here is my current problem with example data.
I am updating the status of a model. The row already exists in the DB. I use PATCH /route/ to get to the controller method. From there I get the model.
class TimecardController extends Controller {
...
public function markAsPass(Request $request, $id) {
$test = Test::findOrFail($id);
//I don't think this is the corect way
//$test->status = "passed";
//$test->markedBy = "Teacher123";
//$test->save();
$test->passed();
...
return redirect($redirect_url);
}
}
class Test extends Model {
...
public function passed() {
$this->status = "passed";
//would I call save here?
//$this->save();
}
}
Do I take an approach like above? Or do I create a service layer where I would use the model instance to call the model functions and then call save on the model?
//in service class
public function makeTestAsPassed($test){
$test->passed();
$test->save();
}
Please let me know if any claification is needed.
You’re right in that business logic belongs in models. If you take a “resourceful” approach to your applications (in that you create controllers around entities) then you’ll find that your controller actions seldom call more than one model method.
Instead of calling save(), you can call create() and update() methods on your model. In your store() controller action, you can create a new entity with one line like this:
public function store(CreateRequest $request)
{
$model = Model::create($request->all());
}
And update an existing model in an update() action like this:
public function update(UpdateRequest $request, Model $model)
{
$model->update($request->all());
}
When it comes to business logic, you can call other methods on your models, too. To use resourceful controllers, you don’t have to have a model that relates to a database table.
Take shipping an order. Most people would be tempted to put a ship() method in an OrderController, but what happens when you ship an order? What entity could shipping an order result in? Well, you’d be creating a shipment, so that could instead be a store() method on an OrderShipmentController. This store() method could then just call a ship() method on your Order model:
class OrderShipmentController extends Controller
{
public function store(ShipOrderRequest $request, Order $order)
{
$order->ship();
}
}
So as you can see, with resourceful controllers and route–model binding, you can have “skinny controllers” with your application’s business logic living in your models.
MVC is designed for ease of maintenance.
The approach you don't recognize as "good" is the proper approach. All data handling related to business logic goes in the controller. Otherwise, a different coder will be confused as he/she would not find the data manipulation logic in the controller code.
Your thin controller goal defeats MVC.
Also note the model code is purposed to be thin as it is a place to define the database schema as mirror image to the database tables.
MVC is not object oriented abstration. MVC is a structure for code maintenance uniformity.
I keen to make my code decouple and ready for testing.
I have an Eloquent model getBudgetConvertedAttribute is depend on sentry user attribute.
public function getBudgetConvertedAttribute()
{
return Sentry::getUser()->currency * $this->budget;
}
This throw error while testing because Sentry::getUser is return null.
My question is, How shall I code to inject user into model from controller or service provider binding or testing?
Inject a $sentry object as a dependency in the constructor instead of using the Sentry Facade.
Example
use Path\To\Sentry;
class ClassName
{
protected $sentry
public function __construct(Sentry $sentry)
{
$this->sentry = $sentry;
}
public function methodName()
{
$this->sentry->sentryMethod();
}
}
Why not just create a method on the model, then takes a Sentry user object as a parameter?
public function getBudgetConverted(SentryUser $user)
{
return $user->currency * $this->budget;
}
You’ll need to change the type-hint (SentryUser) to the actual name of your user class.
If this is to aid testing, you could go one step furhter and type-hint on an interface (which you should be any way), that way you could test your method with a mock user object rather than one that may have a load of other dependencies like a database connection, which Eloquent models do.
I have a problem. I try to get the Entity-Manager without a Controller, but I found no way.
At this time, I get the Entity-Manager like this:
(Controller)
public function getEntityManager()
{
if (null === $this->_em) {
$this->_em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
}
return $this->_em;
}
(Plugin)
public function getEntityManager()
{
if($this->_em == null){
$this->_em = $this->getController()->getServiceLocator()->get('doctrine.entitymanager.orm_default');
}
return $this->_em;
}
You see, I need allways a controller. But, if I need the EntityManager in a model, i have a problem. I can give the model the controller, but I think this is really a bad way.
Have you any idea to get the EntityManager without a controller?
The way I handle Doctrine is through Services, i do it like the following:
//some Controller
public function someAction()
{
$service = $this->getServiceLocator()->get('my_entity_service');
return new ViewModel(array(
'entities' => $service->findAll()
));
}
The Service->findAll() would look something like this:
public function findAll()
{
return $this->getEntityRepository()->findAll();
}
Now we need to define the my_entity_service. I do this inside my Module.php
public function getServiceConfig()
{
return array(
'factories' => array(
'my_entity_service' => 'Namespace\Factory\MyServiceFactory'
)
);
}
Next I create the Factory (note: this could also be done via anonymous function inside the Module.php)
<?php
namespace Namespace\Factory;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
use Namespace\Model\MyModel;
class MyServiceFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$myModel= new MyModel();
$myModel->setEntityManager($serviceLocator->get('Doctrine\ORM\EntityManager'));
return $myModel;
}
}
Now this is a lot to chew :D I totally get that. What is happening here is actually very simple though. Instead of creating your model and somehow get to the EntityManager, you call ZF2's ServiceManager to create the Model for you and inject the EntityManager into it.
If this is still confusing to you, I'll gladly try to explain myself better. For further clarification however I'd like to know about your use case. I.e.: what for do you need the EntityManager or where exactly do u need it.
This code example is outside of the question scope
Just to give you a live example of something I do via ServiceFactories with forms:
public function createService(ServiceLocatorInterface $serviceLocator)
{
$form = new ReferenzwertForm();
$form->setHydrator(new DoctrineEntity($serviceLocator->get('Doctrine\ORM\EntityManager')))
->setObject(new Referenzwert())
->setInputFilter(new ReferenzwertFilter())
->setAttribute('method', 'post');
return $form;
}
Your real question is "How to get an Instance of ServiceManager in my own classes"
For this, take a look at the docu: (bottom of page http://zf2.readthedocs.org/en/latest/modules/zend.service-manager.quick-start.html)
By default, the Zend Framework MVC registers an initializer that will
inject the ServiceManager instance, which is an implementation of
Zend\ServiceManager\ServiceLocatorInterface, into any class
implementing Zend\ServiceManager\ServiceLocatorAwareInterface. A
simple implementation looks like the following.
so implent the ServiceLocatorInterface in your classes and then inside your class you can call:
$this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
or any other service you have registered.