2

I started to read symfony book and try to implement on my own a really basic mvc framework for learning purpose. I use some components of symfony. By now i have an index.php file like this

<?php require_once 'vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; $request = Request::createFromGlobals(); $uri = $request->getPathInfo(); $frontController = new FrontController($uri); 

Then i have a frontController.php file that have a basic routing

<?php class FrontController { private $uri; public function __construct($uri) { $this->uri = $uri; $this->setRoute(); } private function setRoute() { $request = explode("/", trim($_SERVER['REQUEST_URI'])); $controller = !empty($request[1]) ? ucfirst($request[1]) . "Controller" : "IndexController"; $action = !empty($request[2]) ? $request[2] . "Action" : "indexAction"; $parameters = !empty($request[3]) ? intval($request[3]) : null; $response = call_user_func(array($controller, $action), $parameters); $this->sendResponse($response); } private function sendResponse($response) { $response->send(); } } 

Then i have a series of controller (i.e. ordersController.php)

<?php use Symfony\Component\HttpFoundation\Response; class OrdersController { public function ordersAction() { //take data from model $order = new OrdersModel; $orders = $order->get_all_orders(); $html = self::render_template('templates/list.php', array('orders' => $orders)); return new Response($html); } public function render_template($path, array $args = null) { if(!empty($args)) extract($args); ob_start(); require $path; $html = ob_get_clean(); return $html; } } 

Now, although i know that what i have done can be improved , i would like to inject the right model when i instantiate a controller class in the FrontController class without instantiate the model instance in the method where i use that. How can i achieve this? And if you have some suggests to give me, please do that because i want to improve my skills.

2
  • 1
    Dependency Injection is really a big topic. Try reading this. Commented Jan 10, 2016 at 16:02
  • If you're looking to create your own "framework," might I suggest you take a look at Silex. It's a microframework based on Symfony (created by the same ppl who create Symfony) and gives you the bare bones to work with and allows you to customize which components you want. I think it would be a great learning experience for you. Commented Jan 10, 2016 at 16:16

1 Answer 1

1

What are you looking for is a Dependency Injection Container (DIC). It's not only about injecting "models" (actually I hate this word because of ORMs confusion), but rather about injecting any kind of services. DIC incapsulates a service instantiation logic. Then, you can use the DIC inside your FrontController to get required services and pass it to the controller. Also, the DIC introduces some kind of indirection. You don't have to know about actual service implementations, but only about its names.

The simplest DIC implementation for PHP is Pimple. It's very small project, I strictly recommend to look at its source code. Mentioned in the comments Silex uses Pimple under the hood.

To conclude, you have to do only two things:

  • Register a service in the DIC.
  • Inject this service (or a few services) to the controller.

Basic example of registering a service is:

$container['orders'] = function($c) { return new OrdersModel(); }; 

Usually, FrontController has an access to such $container object as a member or FrontController can be a container by itself. Pretty often such object is called Application. Registering of all existing services have to be done before a dispatching code. Since registering a service doesn't require service instantiation (registering a service is just creation of a closure) you can freely register hundreds of such services without an extra load.

Now, you need to somehow specify controller's dependencies. One way to do it - using static field in the controller class.

class OrdersController { public static $dependencies = ['orders', 'otherService']; private $ordersModel; private $otherService; public function __construct($ordersModel, $otherService) { $this->ordersModel = $ordersModel; $this->otherService = $otherService; } } 

And then you have to read list of dependencies of each controller before instantiation in the FrontController:

... $ctrlClass = !empty($request[1]) ? ucfirst($request[1]) . "Controller" : "IndexController"; $services = []; foreach ($ctrlClass::dependencies as $serviceName) { $services[] = $this->container[$serviceName]; } // Here could be better way - PHP is not my native lang. $reflection = new ReflectionClass($classname); $controller = $reflection->newInstanceArgs($services); ... 

That is, by the end you'll get a controller instance with all required dependencies inside. Such code very easy to test.

UPD: Also, a service A can require a service B as internal dependency. Such dependency easy to satisfy in the Pimple approach:

$container['serviceA'] = function($c) { return new ServiceA($c['serviceB']); }; $container['serviceB'] = function($c) { return new ServiceB(); }; 

In such case if your controller requires ServiceA it indirectly depends on ServiceB too. However, you don't have to resolve such indirect dependency manually.

UPD2: Further reading:

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! I will take a look at Pimple and at a links you give me!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.