First, I apologize if this is a stupid question. I recently read an article about repository design pattern and I have a problem when making interface implementation for Laravel Query Builder (Illuminate\Support\Facades\DB).
DatabaseService.php
use Modules\Core\Interfaces\IDatabase;
use \DB;
class DatabaseService implements IDatabase
{
protected $db;
public function __construct(DB $db)
{
$this->db = $db;
}
public function select($str)
{
$this->db::select($str);
return $this->db;
}
public function table($tableName)
{
$this->db::table($tableName);
return $this->db;
}
...
}
IDatabase.php
<?php namespace Modules\Core\Interfaces;
interface IDatabase
{
public function select($str);
public function table($tableName);
public function raw($rawQuery);
public function transaction($callback);
public function first();
public function get();
}
CoreServiceProvider.php
...
public function register()
{
...
$this->app->bind('Modules\Core\Interfaces\IDatabase', function($app) {
$db = $app->make(DB::class);
return new DatabaseService($db);
});
...
}
MailboxRepository.php
<?php namespace Modules\Mailbox\Repositories;
use Illuminate\Database\Eloquent\Model;
use Modules\Core\Interfaces\IDatabase;
use Modules\Mailbox\Interfaces\IMailbox;
class MailboxRepository implements IMailbox
{
public function __construct(..., IDatabase $db)
{
...
$this->db = $db;
}
...
public function getBadges()
{
$badges = $this->db->table('mailbox as a')
->select($this->db->raw(
"SUM(a.type = 'inbox') as inbox,
SUM(a.is_read = 0 AND a.type = 'inbox') as unread,
SUM(a.type = 'sent') as sent,
SUM(a.type = 'draft') as draft,
SUM(a.type = 'outbox') as outbox,
SUM(a.type = 'spam') as spam,
SUM(a.type = 'trash') as trash,
SUM(a.is_starred = 1) as starred"
))
->first();
return $badges;
}
...
}
MailboxServiceProvider.php
<?php namespace Modules\Mailbox;
...
use Modules\Mailbox\Interfaces\IMailbox;
use Modules\Mailbox\Repositories\MailboxRepository;
use Modules\Core\Interfaces\IDatabase;
use Illuminate\Support\ServiceProvider;
class MailboxServiceProvider extends ServiceProvider
{
protected $defer = true;
public function register()
{
$this->app->bind(IMailbox::class, function($app) {
return new MailboxRepository(
..., $app->make(IDatabase::class)
);
});
}
public function provides()
{
return [IMailbox::class];
}
}
With error message :
[2018-01-31 13:45:04] local.ERROR: Call to undefined method Illuminate\Support\Facades\DB::select()
{"userId":1,"email":"info#narpandi.com","exception":"[object]
(Symfony\\Component\\Debug\\Exception\\FatalThrowableError(code: 0): Call
to undefined method Illuminate\\Support\\Facades\\DB::select() at
/var/www/personal-
website/app/Modules/Mailbox/Repositories/MailboxRepository.php:86)
How to do this correctly? Thank you for your kind help.
I don't think this is a common repository pattern, in repository pattern you try to create methods like:
Object get(Object id);
void create(Object entity);
void update(Object entity);
void delete(Object entity);
Edit, try to do something like docs: Database
use Modules\Core\Interfaces\IDatabase;
use Illuminate\Support\Facades\DB;
class DatabaseService implements IDatabase
{
public function select($str, $args)
{
return DB::select($str, $args);
}
}
But i say again this doesn't look like a repository.
After trial and error, I finally figure out how to do this. As mentioned in https://stackoverflow.com/a/26356144/3050636, you cannot use facade DB directly so instead you need to explicitly pass class that is behind the DB facade.
In my case, I use bridge pattern and fluent interface (CMIIW) so I will provide two versions:
Without bridge pattern
MailboxServiceProvider.php
<?php namespace Modules\Mailbox;
use Modules\Mailbox\Interfaces\IMailbox;
...
use Modules\Mailbox\Repositories\MailboxRepository;
use Illuminate\Support\ServiceProvider;
/* Use these instead of DB facade */
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Connectors\ConnectionFactory;
class MailboxServiceProvider extends ServiceProvider
{
protected $defer = true;
public function register()
{
$this->app->bind(IMailbox::class, function($app) {
return new MailboxRepository(
..., new DatabaseManager($app, new ConnectionFactory($app))
);
});
}
public function provides()
{
return [IMailbox::class];
}
}
MailboxRepository.php
<?php namespace Modules\Mailbox\Repositories;
use Illuminate\Database\DatabaseManager;
use Modules\Mailbox\Interfaces\IMailbox;
class MailboxRepository implements IMailbox
{
...
protected $db;
public function __construct(..., DatabaseManager $db)
{
...
$this->db = $db;
}
...
}
With bridge pattern and fluent interface
CoreServiceProvider.php
<?php namespace Modules\Core;
....
use Modules\Core\Services\DatabaseService;
use Modules\Mailbox\Repositories\MailboxRepository;
use Modules\Core\Interfaces\IDatabase;
use Modules\Mailbox\Interfaces\IMailbox;
/* Use these instead of DB facade */
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Support\ServiceProvider;
class CoreServiceProvider extends ServiceProvider
{
public function register()
{
...
$this->app->bind(IDatabase::class, function($app) {
return new DatabaseService(new DatabaseManager($app, new ConnectionFactory($app)));
});
$this->app->bind(IMailbox::class, function($app) {
return new MailboxRepository(
..., $app->make(IDatabase::class)
);
});
...
}
}
DatabaseService.php
<?php namespace Modules\Core\Services;
use Modules\Core\Interfaces\IDatabase;
use Illuminate\Database\DatabaseManager;
class DatabaseService implements IDatabase
{
protected $db;
public function __construct(DatabaseManager $db)
{
$this->db = $db;
}
public function select($str)
{
$this->db = $this->db->select($str);
return $this->db;
}
public function table($tableName)
{
$this->db = $this->db->table($tableName);
return $this->db;
}
...
}
MailboxRepository.php
<?php namespace Modules\Mailbox\Repositories;
use Modules\Core\Interfaces\IDatabase;
use Modules\Mailbox\Interfaces\IMailbox;
class MailboxRepository implements IMailbox
{
...
protected $db;
public function __construct(..., IDatabase $db)
{
...
$this->db = $db;
}
...
}
Related
I used repository in a project that caching all queries.
there's a BaseRepository.
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
class BaseRepository implements BaseRepositoryInterface{
protected $model;
protected int $cacheDuration = 600; //per seconds
public function __construct(Model $model)
{
return $this->model = $model;
}
public function paginate(int $paginate,string $cacheKey)
{
return Cache::remember($cacheKey,$this->cacheDuration , function () use ($paginate) {
return $this->model->latest()->paginate($paginate);
});
}
// other methods ...
}
then i used this repository in my service
PostService:
use Illuminate\Support\Facades\App;
class PostService{
public PostRepositoryInterface $postRepository;
public function __construct()
{
$this->postRepository = App::make(PostRepositoryInterface::class);
}
public function paginate(int $paginate, string $cacheKey)
{
return $this->postRepository->paginate($paginate,$cacheKey);
}
}
finally i using the PostService in my controller
PostController:
class PostController extends Controller{
public PostService $postService;
public function __construct()
{
$this->postService = App::make(PostService::class);
}
public function index()
{
string $cacheKey = "posts.paginate";
return $this->postService->paginate(10);
}
}
the index method will return top 10 latest record correctly. now i need to create a unique CacheKey for all Repository queries. for example
TableName concat FunctionName // posts.paginate
so i can use this code into all method of Repository
public function paginate(int $paginate)
{
$cacheKey = $this->model->getTable().__FUNCTION__;
return Cache::remember($cacheKey,$this->cacheDuration , function () use ($paginate) {
return $this->model->latest()->paginate($paginate);
});
}
this is fine. but the problem is that this code repeat in all the method of this class.
if i use this code in another class, method name's will be incorrect.
What do you suggest to prevent duplication of this code?
I solve this problem by passing function name to another class
I created CacheKey class:
class CacheKey{
public static function generate(Model $model, $functionName):string
{
return $model->getTable()."_".$functionName;
}
}
Then in any method of repository we can use this helper class as follows:
$cacheKey = CacheKey::generate($this->model,__FUNCTION__);
you can easily use magic method in this way:
class CacheService {
private const $cacheableMethods = ['paginate'];
private $otherSerivce;
public __construct($otherSerivce) {
$this->otherSerivce = $otherSerivce;
}
public __get($method, $args) {
if(!in_array($method, static::$cachableMethods)) {
return $this->otherSerivce->{$method}(...$args);
}
return Cache::remember(implode([$method, ...$args], ':'), function () {
return $this->otherSerivce->{$method}(...$args);
});
}
}
I'm building a Lumen app. I'm trying to use interface for my repositories. All my logic is wrapped in a composer package.
Here's my vendor/package/src/app/Providers/PackageServiceProvider.php:
<?php
namespace Vendor\Package\App\Providers;
use Illuminate\Support\ServiceProvider;
class SmsBackendCoreServiceProvider extends ServiceProvider
{
protected $defer = false;
public function register()
{
$this->app->bind(
'Vendor\Package\App\Repositories\Contracts\SmsService',
'Vendor\Package\App\Repositories\Services\SmsJson'
);
}
public function boot()
{
$this->app->group(
['namespace' => 'Vendor\Package\App\Http\Controllers'],
function ($app) {
require __DIR__.'/../../routes/web.php';
}
);
}
}
Here is my vendor/package/src/routes/web.php:
<?php
$app->get('/sms/send.json', 'JsonController#send');
Here is my vendor/package/src/app/Http/Controllers/JsonController.php:
<?php
namespace Vendor\Package\App\Http\Controllers;
use Vendor\Package\App\Http\Controllers\BaseController;
use Vendor\Package\App\Repositories\Contracts\SmsService;
class JsonController extends BaseController
{
public $service;
public function __construct(SmsService $service)
{
$this->service = $service;
}
public function send()
{
$response = $this->service->sendSms(1, 2, 3);
return $response;
}
}
Here is my vendor/package/src/app/Repositories/Contracts/SmsService.php:
<?php
namespace Vendor\Package\App\Repositories\Contracts;
class SmsService
{
public function sendSMS($from, $to, $text);
}
Finally, here is my vendor/package/src/app/Repositories/Services/SmsJson.php:
<?php
namespace Vendor\Package\App\Repositories\Services;
use Vendor\Package\App\Repositories\Contracts\SmsService;
class SmsJson implements SmsService
{
public function sendSMS($from, $to, $text)
{
echo 'success';
}
}
When I try to access http://mydomain.dev/sms/send.json, I get this error:
FatalErrorException in SmsService.php line 7: Non-abstract method
Mitto\SmsBackendCore\App\Repositories\Contracts\SmsService::sendSMS()
must contain body
Where did I go wrong?
Your contract is declared as a class, it must be an interface.
hello am new to phpunit test and am stuck here.
I've followed this tutorial: Zend Framework 2 : Centralize phpunit test
After that i created a module test
namespace ModulesTests\ServiceProvidersTest\Model;
use PHPUnit_Framework_TestCase;
use ModulesTests\ServiceManagerGrabber;
use User\Service\ServiceProvider;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorAwareTrait;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Sql;
class TestServiceProviders extends PHPUnit_Framework_TestCase
{
protected $serviceManager;
protected $serviceprovider;
public function setUp()
{
$serviceManagerGrabber = new ServiceManagerGrabber();
$this->serviceManager = $serviceManagerGrabber->getServiceManager();
$this->serviceprovider = new ServiceProvider() ;
}
public function testSPdetails()
{
$stack = array('1','2');
$this->serviceprovider->getDetails($stack);
}
}
In my ServiceProvider class
namespace User\Service;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorAwareTrait;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Sql;
class ServiceProvider implements ServiceLocatorAwareInterface
{
use ServiceLocatorAwareTrait;
public function getModel()
{
$em = $this->getServiceLocator()- >get('doctrine.entitymanager.orm_default');
return $em->getRepository('User\Entity\ServiceProvider');
}
public function getDetails($data = null,$fields='*')
{
$where = 1;
$company_ids = implode(',',$data);
if(isset($company_ids)){
$where = 'sp.id IN('.$company_ids.')';
}
if(isset($fields)){
}
$db = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
$query = 'some query';
.....Rest code.......
}
}
}
am getting this error :
Call to a member function get() on null in /opt/lampp/htdocs/project/module/User/src/User/Service/ServiceProvider.php
Please help what am missing here..??
So there are a few things I notice here:
1: You have not shown any code showing how you have hooked up your service to the service manager, so it is unclear if this will ever work
2: You directly instantiate your class when you need to be using the service manager grabber you have written
$this->serviceprovider = new ServiceProvider() ;
becomes
$serviceManagerGrabber = new ServiceManagerGrabber();
$this->serviceManager = $serviceManagerGrabber->getServiceManager();
$this->serviceprovider = $this->serviceManager->get('YOUR_SERVICE_KEY');
3: You probably should start with unit tests not these module integration tests being explained in that article. see https://framework.zend.com/manual/2.3/en/modules/zend.test.phpunit.html
4: The ServiceLocatorAwareInterface is deprecated you should probably use a factory and the factories key of service manager config to inject your dependencies
5: Your code seems to mix doctrine and zend db I don't know why you've done this, but my suggestion is ... it's be a bad idea
Here is an example of how you might put this together:
module.config.php
<?php
namespace Application;
return [
'service_manager' => [
'factories' => [
'ServiceProvider' => function ($serviceManager) {
// This shouldn't be in this anon function, it should be its own
// factory but I'm lazy and already writing loads of code for this example
// #see https://framework.zend.com/manual/2.4/en/in-depth-guide/services-and-servicemanager.html#writing-a-factory-class
$service = new \Application\Service\ServiceProvider(
$serviceManager->get('doctrine.entitymanager.orm_default')
);
return $service;
},
]
],
];
ServiceProvider.php
<?php
namespace Application\Service;
use Doctrine\ORM\EntityManager;
class ServiceProvider
{
protected $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function getModel()
{
return $this->entityManager- >getRepository('User\Entity\ServiceProvider');
}
public function getDetails($data = null, $fields='*')
{
$serviceProviderRepository = $this->entityManager->getRepository('User\Entity\ServiceProvider');
return $data;
}
}
ModuleTest
<?php
namespace ModulesTests\Application\Service;
use PHPUnit_Framework_TestCase;
use ModulesTests\ServiceManagerGrabber;
class ServiceProvidersTest extends PHPUnit_Framework_TestCase
{
protected $serviceManager;
protected $serviceprovider;
public function setUp()
{
$serviceManagerGrabber = new ServiceManagerGrabber();
$this->serviceManager = $serviceManagerGrabber->getServiceManager();
$this->serviceprovider = $this->serviceManager->get('ServiceProvider');
}
public function testSPdetails()
{
$stack = array('1','2');
$this->serviceprovider->getDetails($stack);
}
}
unit test:
<?php
namespace ModulesTests\Application\Service;
use PHPUnit_Framework_TestCase;
use Application\Service\ServiceProvider;
use Prophecy\Argument;
class ServiceProvidersUnitTest extends PHPUnit_Framework_TestCase
{
protected $entityManager;
protected $serviceprovider;
public function setUp()
{
$this->entityManager = $this->prophesize("Doctrine\\ORM\\EntityManager");
$this->entityManager->getRepository(Argument::exact('User\Entity\ServiceProvider'))
->willReturn(true);
$this->serviceprovider = new ServiceProvider($this->entityManager->reveal());
}
public function testSPdetails()
{
$stack = array('1','2');
$this->serviceprovider->getDetails($stack);
$this->entityManager->getRepository(Argument::exact('User\Entity\ServiceProvider'))
->shouldHaveBeenCalledTimes(1);
}
}
I know this looks like a dupllicate of Inject Silex $app in my custom class and others, but I couldn't get it working from their solutions.
I define my service like this:
$app['user.repo'] = function () {
return new MyApp\Repository\User();
};
My class looks like this:
<?php
namespace MyApp\Repository;
use Silex\Application;
class User {
public function findAll(Application $app) {
$users = $app['db']->fetchAll('SELECT * FROM user');
return $users;
}
}
And I use the service like this:
$users = $app['user.repo']->findAll($app);
How can I do this same thing without putting $app in all my methods?
Why don't you inject it?
$app['user.repo'] = function () use ($app) {
return new MyApp\Repository\User($app);
};
And here's your modified class:
<?php
namespace MyApp\Repository;
use Silex\Application;
class User {
/** #var Application */
protected $app;
public function __construct(Application $app) {
$this->app = $app;
}
public function findAll() {
$users = $app['db']->fetchAll('SELECT * FROM user');
return $users;
}
}
Or even better: instead of injecting the whole application (and thus hiding your real dependencies, making unit testing a pain), only inject what you really need:
$app['user.repo'] = function () use ($app) {
return new MyApp\Repository\User($app["db"]);
};
This way your class becomes:
<?php
namespace MyApp\Repository;
use Silex\Application;
class User {
protected $db;
public function __construct($db) {
$this->db = $db;
}
public function findAll() {
$users = $this->db->fetchAll('SELECT * FROM user');
return $users;
}
}
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())
);
}
}