I'm trying to utilize the Laravel database contract instead of the facade (DB::table) and getting:
ReflectionException
Class Illuminate\Contracts\Database does not exist
Using the Illuminate\Database\DatabaseManager class works fine, but I would prefer not use a concrete class but rather an abstract class (contract/interface) in addition to dependency injection.
Here's what I've tried.
Controller:
<?php namespace App\Http\Controllers;
use Illuminate\Contracts\Database as DB;
class MyController extends Controller
{
public function __construct(DB $db)
{
$this->db = $db;
}
}
AppServiceProvider:
public function register()
{
$this->app->bind(
'Illuminate\Contracts\Auth\Registrar',
'App\Services\Registrar',
'Illuminate\Contracts\Database'
);
}
Any help appreciated!
That Contract does not exist unless you added it yourself. Illuminate\Contracts\Redis\Database exists, but not what you are trying to use.
As for a Solution, I generally inject the Illuminate\Database\DatabaseManager class instead, which I believe is the class you are trying to get anyway. That class implements the Illuminate\Database\ConnectionResolverInterface interface.
Related
In Codeignter 4 we can't use constructor in BaseController. But the method initController() will do it. But how can call this method from derived controller's constructor?
My question is that BaseController::is_allowed() method will do all basic features that are commonly useful for all derived controller classes. But to work BaseController::is_allowed() , BaseController::__construct() should be execute before this. But as in CI-4, constructor is not allowed in BaseController. It can have BaseController::initController(). But the problem is that this method will execute only after DerivedClass::__construct().
I need to execute BaseController::is_allowed() before executing every derived class methods. So I call BaseController::is_allowed() method in constructor of derived controllers. But derived class constructor executes before the execution of BaseController::initController(). So BaseController::is_allowed() not works.
BaseController.php
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
class BaseController extends Controller
{
public $request;
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
{
parent::initController($request, $response, $logger);
// Doing all basic setups here that are needed to all other methods in this class.
// This method will be called only after derivedClass::__construct().
// But CI-4 not allows to use __construct() method in BaseController class.
// This causes my problem.
}
function is_allowed()
{
// Provides all basic features for all derived controller classes.
// But to work code in this method, initController() method should execute first.
}
}
And the derived class as
Users.php
<?php
namespace App\Controllers;
class Users extends BaseController
{
public function __construct()
{
// BaseController::is_allowed() will provide all basic features for this controller.
// To work this method, BaseController::initController() should execute.
// But this will execute only after this ( __construct()) constuctor.
// In Codeignier-3, BaseController::__construct() was possible.
// It will execute before derived class constructor.
$this->is_allowed();
}
}
Basically your Users Controller should use the iniController and not the construct, like so:
<?php
namespace App\Controllers;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
class Users extends BaseController
{
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
{
parent::initController($request, $response, $logger);
$this->is_allowed();
}
}
However its a good practice to create your is_allowed function in your BaseController as a protected function, otherwise one might be able to access it via any url like site.com/users/is_allowed
I might even add that if the purpose of the is_allowed function is to check if the user has permission to do an action or even be in that controller your should look into Filters and not this
In Codeignter 4 we can't use constructor in controllers.
You don't use a typical constructor in the BaseController class, you can still use constructors in your App/Controller classes.
You do not need to manually make a call to initController, that is done for you during the bootstrap process. Remove that line from your App\Controllers\Users constructor function.
I am trying to separate functionalities into two classes and I want to Inject one class into another. However, it seems that Laravel can't recognize the second class and it is always null.
namespace App\Services;
use App\Models\Image;
use App\Models\Offer;
class ImagesService {
public function __construct() {
}
function saveImages(iterable $images, Offer $offer): array {
// ... code here !
}
}
And the class to inject in is:
namespace App\Services;
use App\Models\Action;
use App\Models\Offer;
use App\Models\OfferOption;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class OffersService {
protected $imagesService;
function __constructor(ImagesService $imgService) {
$this->imagesService = $imgService; //Doesn't work ! $imagesService is always null!
}
function doSomething() {
$this->imagesService->saveImages(....) // Call to a member function saveImages() on null at
}
}
there are several ways to access and use properties from one class to another class.
Create an instance of the class you want to use in the class you want to use.
Use the class you want to use as Trait and call it use in the other class.
Extend the class you want to use to the class you want to use.
I'm trying to use traits in CodeIgniter. I put the Trait in the libraries directory and then loaded the library in the controller. Then I used the trait in the model, but it didn't work and I received the following error
Trait file (in library):
trait DataTablesTrait{
public function query(){
$this->db->select('*');
$this->db->limit(10, 1);
return $this->db->get('my_table');
}
}
?>
Controller:
class myController extends CI_Controller {
public function __construct(){
parent::__construct();
$this->load->library('DataTablesTrait');
$this->load->model('ABC_Model');
}
}
Model:
class ABC_Model extends CI_Model {
use DataTablesTrait;
function callQuery(){
$this->query();
}
}
I got this error:
Non-existent class: DataTablesTrait
Please advise
CodeIgniter (CI) isn't trait or namespace friendly but they can be used. It requires working around CI a bit though.
The main problem, the CI thing you have to work around, is the call
$this->load->library('DataTablesTrait');
CI will find this file but load->library will try to instantiate the trait which fails because it is not possible to instantiate a Trait on its own.
Replace the line above with
include APPPATH.'/libraries/DataTablesTrait.php';
You should be free of the error with that. But you're not going to get results because callQuery() does not return anything. To round out the test have callQuery() return the CI_DB_result object the Trait should produce
public function callQuery()
{
return $this->query();
}
Add an index function to the controller so we can dump the output
public function index()
{
$DB_result = $this->ABC_Model->callQuery();
var_dump($DB_result->result());
}
This should produce the expected output - assuming that 'my_table' has data :)
Worth saying that traits are now fully supported in Codeigniter 4 and can be implemented as you would expect using namespaces etc.
// TRAIT
namespace App\Controllers\traits;
trait YourTraitName {
}
// CONTROLLER
use App\Controllers\traits\YourTraitName;
class Admin extends BaseController {
use YourTraitName;
I encountered this issue using the repository pattern. Currently I use an interface, and a custom class to achieve it, then type-hint it into the controller's construct and because of Laravel, it will solve the repositories' dependencies automatically and recursively.
I also do this in a service provider:
$this->app->bind(path/to/repoInterface,path/to/implementationClass)
However, because of the way I coded these repositories, in order to avoid code duplication, I created an abstract class that has a common method to all these repositories. This class is as follows:
abstract class CommonRepo{
public function __construct(SomeModelClass model){}
public function commonMethod(){//Code here}
And my repositories have the following structure:
public class ExampleRepository extends CommonRepo implements ExampleRepositoryI{
public function __construct(){
parent::__construct();
}
}
Laravel doesn't like this, so its giving this error:
Argument 1 passed to path/to/repo/CommonRepo::__construct() must be an instance of path/to/model/SomeModelClass, none given, called in...
So, obviously is not resolving the dependency of the class CommonRepo, but it does resolve the dependencies on the normal repositories.
I'd like, if it's possible, to use type-hinting (the Laravel way) without having to do anything related to the new operator
How can I, then, resolve that class's dependencies ?
PD: Using Laravel 5.2
Parent constructor is called like normal function without touching dependency resolver so you should do one of two possibilities:
public class ExampleRepository extends CommonRepo implements ExampleRepositoryI
{
public function __construct(SomeModelClass $model){
parent::__construct($model);
}
}
or
public class ExampleRepository extends CommonRepo implements ExampleRepositoryI
{
public function __construct(){
parent::__construct(App::make(SomeModelClass::class));
}
}
nice question. I did some tinkering, though I don't know if this is what you're looking for. But you can dynamically create an instance of Eloquent model required by your repository class.
Let's say you have your User model class stored in app\Models\User.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
//
}
You then create a base abstract class for all of your repository classes: app\Repositories\BaseRepository.php. This is where you place all common functionalities for your repository classes. But rather than injecting the Eloquent instance through the constructor, you may add a method named getModel() to dynamically create an instance of Eloquent model for your repository.
<?php
namespace App\Repositories;
use ReflectionClass;
use RuntimeException;
use Illuminate\Support\Str;
abstract class BaseRepository
{
protected $modelNamespace = 'App\\Models\\';
public function getById($id)
{
return $this->getModel()->find($id);
}
public function getModel()
{
$repositoryClassName = (new ReflectionClass($this))->getShortName();
$modelRepositoryClassName = $this->modelNamespace . Str::replaceLast('Repository', '', $repositoryClassName);
if (! class_exists($modelRepositoryClassName)) {
throw new RuntimeException("Class {$modelRepositoryClassName} does not exists.");
}
return new $modelRepositoryClassName;
}
}
Now let's say you want to create a repository for your User model, and this user's repository must implement the following interface: app\Repositories\UserRepositoryInterface.php
<?php
namespace App\Repositories;
interface UserRepositoryInterface
{
public function getByEmail($email);
}
You create app\Repositories\UserRepository.php class and simply extend it from the BaseRepository class. Also don't forget to implement all specific implementations defined on UserRepositoryInterface.
<?php
namespace App\Repositories;
use App\Repositories\BaseRepository;
use App\Repositories\UserRepositoryInterface;
class UserRepository extends BaseRepository implements UserRepositoryInterface
{
public function getByEmail($email)
{
return $this->getModel()->where('email', $email)->firstOrFail();
}
}
This way you can bind the UserRepositoryInterface to it's implementation like so:
$this->app->bind(\App\Repositories\UserRepositoryInterface::class, \App\Repositories\UserRepository::class);
Finally you can freely inject the UserRepositoryInterface to a controller's constructor or methods. You can also resolve it via service container like this:
$userRepository = App::make(App\Repositories\UserRepositoryInterface::class);
$userRepository->getByEmail('john#example.com');
Of course there's a catch to this approach. The repository class should be started with the associated model, so the InvoiceRepository.php is dedicated for Invoice.php model class.
Hope this help!
This might help. You can listen in for when an object resolves and set attributes.
$this->app->resolving(CommonRepo::class, function ($object, $app) {
// Called when container resolves object of any type...
$object->commonObject = app(CommonObject::class);
});
Docs: https://laravel.com/docs/5.4/container#container-events
I have a service provider that I want to use to bind an instance of a class to the service container:
namespace App\Providers;
use Eluceo\iCal\Component\Calendar;
use Illuminate\Support\ServiceProvider;
class IcalProvider extends ServiceProvider
{
public function register()
{
$this->app->instance('iCal', function () {
return new Calendar(config('calendar.name'));
});
}
}
As I understand the documentation on binding an instance, this allows me to bind the key iCal to the service container so that later in my controller or service class I can type hint iCal and the instance created in the service provider will be used.
So I created a controller and tried to type hint my instance:
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
class CalendarInviteController extends Controller
{
public function download(iCal $ical, $sessionId)
{
dd($ical);
}
}
But when I do I get the error:
Class App\Http\Controllers\iCal does not exist
Makes sense, as it applies it's looking for a class named iCal in the controller namespace which doesn't exist. There's not a use statement for the instance since iCal is just a text key, so I tried telling it to look at the root namespace thinking that may fix it:
public function download(\iCal $ical, $sessionId)
and I get the error:
Class iCal does not exist
When I read the section of the documentation on resolving from the service container it looks like the only thing I need to do in the controller is type hint to get the instance.
Am I misunderstanding the docs?
Update
I should also mention that I did add my service provider to my config/app.php file.
Also, when I create an interface, bind it to the service container instead, edit the vendor code to implement said interface, and inject that interface instead it works, but that requires that I edit the vendor code which I don't want.
As you see in the docs the method instance takes a key and an object instance to register in the container. So, if you want to register a specific instance in the container, the registration should be:
namespace App\Providers;
use Eluceo\iCal\Component\Calendar;
use Illuminate\Support\ServiceProvider;
class IcalProvider extends ServiceProvider
{
public function register()
{
//register a specific instance of the Calendar class in the container
$this->app->instance('iCal', new Calendar(config('calendar.name') );
}
}
This way you could get back the instance with:
$cal = \App::make('iCal');
If your purpose is to type-hint the class in the controller method, and you want to resolve the previous registered instance from the service container, you could do like this:
namespace App\Providers;
use Eluceo\iCal\Component\Calendar;
use Illuminate\Support\ServiceProvider;
class IcalProvider extends ServiceProvider
{
public function register()
{
//the key will be 'Eluceo\iCal\Component\Calendar'
$this->app->instance( Calendar::class, new Calendar(config('calendar.name') );
}
}
Now, in your controller:
namespace App\Http\Controllers;
//important: specify the Calendar namespace
use Eluceo\iCal\Component\Calendar;
class CalendarInviteController extends Controller
{
public function download(Calendar $ical, $sessionId)
{
dd($ical);
}
}
This way Laravel will see that you want a Calendar object and it will try to get it from the service container looking if exists a binding for this key: (because this is the namespace of the class you have specified in the controller)
Eluceo\iCal\Component\Calendar
and the binding exists! As you have bound this key to your service container in your service provider, so Laravel will return your registered instance.
In the code you provided, you tipe-hinted the class iCal, but the class didn't exist anywhere so Laravel wasn't able to instantiate the class
If you’re wanting to inject dependencies into your controller (which is good, so kudos!) then you need an interface name to type-hint on.
Usually you would have a generic interface, and then bind that interface to a concrete implementation. So you may have a calendar service interface, that’s bound to your iCal implementation. Something like this:
use Eluceo\iCal\Component\Calendar;
class CalendarServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('App\Services\Calendar', function ($app) {
return new Calendar(config('calendar.name'));
});
}
public function provides()
{
return ['App\Services\Calendar'];
}
}
So long as you register your service provider in your config/app.php file, you can now type-hint your calendar dependency in classes:
use App\Services\Calendar;
class InvitationController extends Controller
{
protected $calendar;
public function __construct(Calendar $calendar)
{
$this->calendar = $calendar;
}
}