I'm newbie in working with framework and Silex,
I Trying to work with Silex and write my first project.
I use this silex-bootstrap : https://github.com/fbrandel/silex-boilerplate
and now in my app/app.php :
<?php
require __DIR__.'/bootstrap.php';
$app = new Silex\Application();
$app->register(new Silex\Provider\ServiceControllerServiceProvider());
// Twig Extension
$app->register(new Silex\Provider\TwigServiceProvider(), array(
'twig.path' => __DIR__.'/views',
));
// Config Extension
$app->register(new Igorw\Silex\ConfigServiceProvider(__DIR__."/config/config.yml"));
$app->get('/admin', new App\Controller\Admin\AdminDashboard());
return $app;
and in app/Controller/Admin/AdminDashboard.php :
<?php
namespace App\Controller\Admin;
use Silex\Application;
use Silex\ControllerProviderInterface;
use Silex\ControllerCollection;
class AdminDashboard implements ControllerProviderInterface {
function __construct()
{
return "Dashboard";
}
function index()
{
return "Index Dashboard";
}
public function connect(Application $app)
{
return "OK";
}
}
When I trying to access to site I get this error:
http://localhost/project/public
InvalidArgumentException in ControllerResolver.php line 69:
Controller "App\Controller\Admin\AdminDashboard" for URI "/admin" is not callable.
What should I do ?
You're trying to use a controller provider as the actual controller. These are two different things. The provider simply registers controllers with your silex app. Your provider should look something like this:
namespace App\Controller\Admin;
use Silex\Application;
use Silex\ControllerProviderInterface;
class AdminDashboardProvider implements ControllerProviderInterface
{
public function connect(Application $app)
{
$controllers = $app['controllers_factory']();
$controllers->get('/', function() {
return 'Index Dashboard';
});
return $controllers;
}
}
Then you should mount that controller provider to your app in app/app.php.
$app->mount('/admin', new AdminDashboardProvider());
Obviously, this is not very elegant once you get more than a few controllers or if your controllers get big. That's where ServiceControllerServiceProvider comes in. It allows your controllers to be separate classes. I typically use a pattern like this:
<?php
namespace App\Controller\Admin;
use Silex\Application;
use Silex\ControllerProviderInterface;
class AdminDashboardProvider implements ControllerProviderInterface, ServiceProviderInterface
{
public function register(Application $app)
{
$app['controller.admin.index'] = $app->share(function () {
return new AdminIndexController();
});
}
public function connect(Application $app)
{
$controllers = $app['controllers_factory']();
$controllers->get('/', 'controller.admin.index:get');
return $controllers;
}
public function boot(Application $app)
{
$app->mount('/admin', $this);
}
}
The controller looks like this:
namespace App\Controller\Admin;
class AdminIndexController
{
public function get()
{
return 'Index Dashboard';
}
}
Then you can register it with your app in app/app.php like:
$app->register(new AdminDashboardProvider());
Related
I am working on a Slim Framework based API. Following the mvc pattern, I want my routes to be controller driven with logger and renderer injected to every controller.
As a start point I checked out a number of example mvc slim skeletons, and decided to base my structor on one particular tutorial and sample project ( http://jgrundner.com/slim-oo-004-controller-classes/ )
In this setup, injection is done by adding the router controllers to the app container like this:
$container = $app->getContainer();
$container['\App\Controllers\DefaultController'] = function($c){
return new \App\Controllers\DefaultController(
$c->get('logger'),
$c->get('renderer')
);
};
This then allow for a nice clean route and controller:
route e.g.:
$app->get('/[{name}]', '\App\Controllers\DefaultController:index');
controller e.g.:
namespace App\Controllers;
use Psr\Log\LoggerInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
class DefaultController{
private $logger;
private $renderer;
public function __construct(LoggerInterface $logger, $renderer){
$this->logger = $logger;
$this->renderer = $renderer;
}
public function index(RequestInterface $request, ResponseInterface $response, $args){
// Log message
$this->logger->info("Slim-Skeleton '/' route");
// Render index view
return $this->renderer->render($response, 'index.phtml', $args);
}
public function throwException(RequestInterface $request, ResponseInterface $response, array $args){
$this->logger->info("Slim-Skeleton '/throw' route");
throw new \Exception('testing errors 1.2.3..');
}
}
Extending the default controller keeps the controllers neat, but requires adding every new controller object to the app container first, which seems inefficient and messy when you have a lot of classes.
Is there a better way?
You can make basecontroller that contain the acontainer.
<?php
namespace App\Controller;
use Slim\Container;
class Controller
{
var $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function __get($var)
{
return $this->container->{$var};
}
}
and the container:
<?php
$container['App\Controller\Controller'] = function ($c) {
return new App\Controller\Controller($c);
};
And the controller
<?php
namespace App\Controllers;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use App\Controller\Controller;
class DefaultController extends Controller{
}
sorry for my english.
stack: Slim 3 framework + Eloquent ORM.
Eloquent works as expected with Slim.
I want to use sort of a MVC pattern where thin controllers and fat models(all db queries and other heavy logic).
All I found is how to use it from routes like this:
$app->get('/loans', function () use ($app) {
$data = DB::table('loan_instalment')->get(); // works
$data = $this->db->table('loan_instalment')->get(); // works
...
}
What I want is ability to call public methods from choosen model, something like this:
use \src\models\Instalment;
$app->get('/loans', function () use ($app) {
$data = $this->model('Instalment')->getSomething(12);
...
}
and Model class is:
namespace src\models;
use Illuminate\Database\Eloquent\Model as Model;
use Illuminate\Database\Capsule\Manager as DB;
class Instalment extends Model
{
protected $table = 'loan_instalment';
public function getSomething($id)
{
return $this->table->find($id);
}
// bunch of other methods
}
My app looks like basic Slim skeleton, Eloquent settings:
$capsule = new \Illuminate\Database\Capsule\Manager;
$capsule->addConnection($container['settings']['db']);
$capsule->setAsGlobal();
$capsule->bootEloquent();
$container['db'] = function ($container) use ($capsule){
return $capsule;
};
Is it possible ?
If you want to use MVC pattern, you need to make base controller.
<?php
namespace App\Controller;
use Slim\Container;
class BaseController
{
protected $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function getContainer()
{
return $this->container;
}
public function __get($name)
{
return $this->container->{$name};
}
public function __set($name, $value)
{
$this->container->{$name} = $value;
}
}
And the container:
// Base Controller
$container[App\Controller\BaseController::class] = function ($c) {
return new App\Controller\BaseController($c);
};
$capsule = new \Illuminate\Database\Capsule\Manager;
$capsule->addConnection($container['settings']['db']);
$capsule->setAsGlobal();
$capsule->bootEloquent();
$container['db'] = function ($container) use ($capsule){
return $capsule;
};
Highly recommended to use static function on models
<?php
namespace App\models;
use Illuminate\Database\Eloquent\Model as Model;
use Illuminate\Database\Capsule\Manager as DB;
class Instalment extends Model
{
protected $table = 'loan_instalment';
public static function getSomething($id)
{
return Instalment::find($id);
}
}
And now you code become:
<?php
use App\models\Instalment;
$app->get('/loans', function ($request, $response, $args) {
$data = Instalment::getSomething(12);
...
}
The controller:
<?php
namespace App\Controller;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use App\models\Instalment;
class HomeController extends BaseController
{
public function __invoke(Request $request, Response $response, Array $args)
{
$data = Instalment::getSomething(12);
// load the template
return $response;
}
}
And the route for the controller
<?php
$app->get('/', App\Controller\HomeController::class);
It looks cleaner, isn't it?
More tutorial:
My Blog
Rob Allen's Blog
You could use the abbility of Slim to use controllers.
Make a basic controller:
// BasicController.php
<?php
namespace src\Controllers;
class BasicController
{
public function model(string $model)
{
return new $model();
}
}
and then in your controllers extend this class and add it to the slim container
//SomeController.php
<?php
namespace src\Controllers;
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
use \src\models\Instalment as Instalment;
class SomeController extends BasicController
{
public function index(Request $request, Response $response, $args)
{
$this->model(Instalment::class)->getSomethingOutOfDB;
//do something else
}
}
...
//container.php
use \Slim\Container as Container;
$container[\src\Controllers\HomeController::class] = function(Container $container) {
return new \src\Controllers\Homecontroller();
}
...
...
//routes.php
$app->get('/someroute', \src\Controllers\HomeController::class . ':index');
...
Another possibility is to extend your \Slim\App by:
//newApp.php
namespace scr\App
class newApp extends \Slim\App
{
public function model(string $model)
{
return new $model();
}
}
I actually would advice against these both methods, and not load your models in this way, since this is considered bad practice.
It is better just to use:
//routes.php
...
use src\Models\Instalment;
...
...
$app->get('/someroute', function() {
$instalment = new Instalment();
// do something with it...
});
If I had the following code how would I get the calling method and the class in the provider:
class HelloServiceProvider implements ServiceProviderInterface {
public function register(Application $app){
$app['hello'] = $app->share(function () {
// Get hello/indexAction
});
}
public function boot(Application $app){}
}
class hello {
public function addAction(){
$app['hello']()
}
}
$app->get('/hello', 'hello.controller:indexAction');
Is this even possible? Thanks
Is it, indeed, possible but you need some changes:
<?php
// file HelloServiceProvider.php
class HelloServiceProvider implements ServiceProviderInterface {
public function register(Application $app){
$app['hello'] = $app->share(function () {
// Get hello/indexAction
});
}
public function boot(Application $app){}
}
// file Hello.php
class Hello {
public function indexAction(Application $app){
$app['hello']()
}
}
// somewhere in your code:
$app->register(new Silex\Provider\ServiceControllerServiceProvider());
$app->register(new HelloServiceProvider());
$app['hello.controller'] = $app->share(function() {
return new hello();
});
$app->get('/hello', 'hello.controller:indexAction');
Notice that the use statements are missing from code
You can get more information in the official documentation.
My Controller :
<?php
namespace Admin\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Admin\Controller;
use Admin\Service;
class AdminController extends AbstractActionController
{
public function indexAction()
{
$CrudService = $this->getServiceLocator()->get('CrudService');
return new ViewModel(
array('list'=> $CrudService->getList())
);
}
}
Service Layer :
<?php
namespace Admin\Service;
use Admin\Dao;
class CrudService
{
public function getList()
{
$CrudDao=new Dao\CrudDao();
$list=$CrudDao->getList();
return $list;
}
}
Dao Layer :
<?php
namespace Admin\Dao;
class CrudDao
{
public function getList()
{
return
$this->getServiceLocator()->
get('doctrine.entitymanager.orm_default')->
getRepository('Admin\Entity\ProductEntity')
->findAll();
}
}
every things is good work But My Problem is Dao Layer. that give me This
Error : not Found get Service Locator Class
I want get data From Doctrine in Dao Layer and Call Dao Method in Service Layer And Next Call Service With getServiceLocator in Controller
You have to inject all dependencies and use the service manager to get the classes.
In you Module.php you have to register and inject the dependencies:
class Module
{
// ...
public function getServiceConfig()
{
$factories = [
'Admin\Dao\CrudDao' = function (ServiceManager $serviceManager) {
$entityManager = $serviceManager->get('Doctrine\ORM\EntityManager'),
return new CrudDao($entityManager);
},
'Admin\Service\CrudService' = function (ServiceManager $serviceManager) {
return new CrudService($serviceManager);
}
];
return $factories;
}
}
The Dao will receive the EntityManager:
<?php
namespace Admin\Dao;
class CrudDao
{
private $entityManager;
public function __construct($entityManager)
{
$this->entityManager = $entityManager;
}
public function getList()
{
return
$this->entityManager
->getRepository('Admin\Entity\ProductEntity')
->findAll();
}
}
Your CrudService will receive the Service manager, then you can get the CrudDao:
<?php
namespace Admin\Service;
use Admin\Dao;
class CrudService
{
public function __construct($serviceManager)
{
$this->serviceManager = $serviceManager;
}
public function getList()
{
$CrudDao= $this->serviceManager->get('Admin\Dao\CrudDao');
$list = $CrudDao->getList();
return $list;
}
}
And your controller:
<?php
namespace Admin\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Admin\Controller;
use Admin\Service;
class AdminController extends AbstractActionController
{
public function indexAction()
{
$CrudService = $this->getServiceLocator()->get('Admin\Service\CrudService');
return new ViewModel(
array('list'=> $CrudService->getList())
);
}
}
I have the following DemoController
class DemoController {
public function test() {
return new Response('This is a test!');
}
}
I want to bind this controller to $app ['demo.controller']
$app ['demo.controller'] = $app->share ( function () use($app) {
return new DemoController ();
} );
Inside the DemoController i want to have the Application $app object to work with registered services. What is the right way? Currently i am using __construct($app) for the DemoController and pass $app. This looks like
$app ['demo.controller'] = $app->share ( function () use($app) {
return new DemoController ($app);
} );
What is the best-practice for that?
That is certainly one way to do it. I want to show two alternatives.
One is to get the application injected into the action method directly using a type hint:
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
class DemoController
{
public function test(Request $request, Application $app)
{
$body = 'This is a test!';
$body .= ':'.$request->getRequestUri();
$body .= ':'.$app['foo']->bar();
return new Response($body);
}
}
The advantage of this option is that you don't actually need to register your controller as a service.
The other possibility is to inject the specific service instead of injecting the whole container:
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
class DemoController
{
private $foo;
public function __construct(Foo $foo)
{
$this->foo = $foo;
}
public function test()
{
return new Response($this->foo->bar());
}
}
Service definition:
$app['demo.controller'] = $app->share(function ($app) {
return new DemoController($app['foo']);
});
The advantage of this option is that your controller no longer depends on silex, the container or any particular service name. This makes it more isolated, re-usable and easier to test in isolation.