I have a class in Laravel with a class variable that holds and object
class RegisterController extends Controller {
public $company;
When i set the variable in my index method all goes well
public function index($id = null) {
$this->company = new Teamleader\Company;
When I try to access $this->company from another method it returns
null
This is my full code
class RegisterController extends Controller {
public $company;
public function index($id = null)
{
$this->company = new Teamleader\Company;
// returns ok!
dd($this->company);
return view('register.index');
}
public function register()
{
// returns null
dd($this->company);
}
}
Am I missing something?
Thank you!
You are not __constructing() the class, you are just assigning variable inside a function inside a class, which means it is encapsulated into that function inside that class.
So if you would like to make $this->company global in class, you could use
public function __construct() {
$this->company = new Teamleader\Company;
}
In Laravel 5 you can inject a new instance of Teamleader\Company into the methods you need it available in.
use Teamleader\Company;
class RegisterController extends Controller {
public function index($id = null, Company $company)
{
dd($company);
}
public function register(Company $company)
{
dd($company);
}
}
For Laravel <5 dependency inject into the constructor.
use Teamleader\Company;
class RegisterController extends Controller {
protected $company;
public function __construct(Company $company)
{
$this->company = $company;
}
public function index($id = null)
{
dd($this->company);
}
public function register()
{
dd($this->company);
}
}
Dependency injection is better than manual invocation as you can easily pass a mock object to this controller during testing. If you're not testing, maybe someone else will be in the future, be kind. :-)
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 have a simple code that displays to the user all of his notifications received from the Database:
$user_notifications = DB::table('notifications')->where('user_id', $this->user->id)->orderBy('id', 'desc')->get();
the problem is that I have too many controllers and functions in them and I don’t want to duplicate this code everywhere, in each function and controller. How can I make the $user_notifications variable global and use it everywhere?
u need to create a NotificationRepository
class NotificationsRepository
{
private Notifications $model;
public function __construct(Notification $model)
{
$this->model = $model;
}
public function findByUserId(int $userId): Collection
{
return $this->model->where('user_id', $userId)->orderBy('id', 'desc')->get();
}
}
Then in Controller action add this repository by autowiring
class SomeController extends Controller
{
public function someAction(NotificationRepository $repository, int $id)
{
$notifications = $repository->findByUserId($id);
}
}
or this way, i dont know how did u use your actions
class SomeController extends Controller
{
public function someAction(NotificationRepository $repository, Illuminate\Http\Request $request)
{
$notifications = $repository->findByUserId($request->user()->id);
}
}
I am following this link to implement it
I did below steps to implement the Contract in my existing class.
Below is the class where I will write some logic also before sending it to controller
namespace App\Classes\BusinessLogic\Role;
use App\Classes\DatabaseLayer\Role\RoleDb;
use App\Classes\Contract\Role\IRole;
class RoleBL implements IRole {
public function All() {
return (new RoleDb())->All();
}
}
Database Function
namespace App\Classes\DatabaseLayer\Role;
class RoleDb {
public function All() {
$Roles = \App\Models\Role\RoleModel
::all();
return $Roles;
}
}
Interface
namespace App\Classes\Contract\Role;
interface IRole {
public function All();
}
Service Provider class
namespace App\Providers\Role;
class RoleServiceProvider extends \Illuminate\Support\ServiceProvider {
public function register()
{
$this->app->bind('App\Classes\Contract\Role\IRole', function($app){
return new \App\Classes\BusinessLogic\Role\RoleBL($app['HttpClient']);
});
}
}
Finally in config/app.php in provider wrote below line.
App\Providers\Role\RoleServiceProvider::class
Controller - Constructor
protected $roles;
public function __construct(\App\Classes\Contract\Role\IRole $_roles) {
parent::__construct();
$roles = $_roles;
}
Controller Action method
public function index(IRole $roles) {
$RoleTypes = $roles->All();
}
So far everything works fine if I keep Interface as parameter in method.
if I try to use the variable $roles in index method and remove the variable, it is always null.
Please guide me if I missed anything?
You incorrectly assign the $roles property in your __construct() method.
Replace
$roles = $_roles;
with
$this->roles = $_roles;
and then in your index method do:
$RoleTypes = $this->roles->All();
I'm developing an API and in the controller the index, store, show, update and destroy methods are all the same except for the Model that is being used.
How would you implement this?
I was thinking about a ActionRepository where I create those methods and will resolve the model somehow. I am not sure how I can reach the model though..
Really would appreciate some feedback on this ;)!
You can do this:
abstract class BaseController extends LaravelController {
protected $repository; // or $model or whatever you need
public function index() { // your logic }
public function show($id) {
// your logic here, for example
return $this->repository->find($id);
// or
return $this->model->find($id);
}
public function create() { // your logic }
public function store() { // your logic }
public function edit($id) { // your logic }
public function update($id) { // your logic }
public function destroy($id) { // your logic }
}
class SomeSolidController extends BaseController {
public function __construct(SomeRepositoryInterface $repository)
{
$this->repository = $repository;
}
}
I am trying to test my controller with this test (I'm using Laravel, if that matters):
<?php
use Zizaco\FactoryMuff\Facade\FactoryMuff;
class ProjectControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
$this->mock = $this->mock('Dumminvoicing\Storage\Project\ProjectRepositoryInterface');
}
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
protected function tearDown()
{
Mockery::close();
}
public function testRedirectWhenNotLogged()
{
Route::enableFilters();
$response = $this->call('GET', 'projects');
$this->assertRedirectedToAction('UserController#getLogin');
}
public function testAllowedWhenLogged()
{
Route::enableFilters();
//Create user and log in
$user = FactoryMuff::create('User');
$this->be($user);
$response = $this->call('GET', 'projects');
$this->assertResponseOk();
}
public function testIndex()
{
$this->mock->shouldReceive('all')->once();
$this->call('GET', 'projects');
$this->assertViewHas('projects');
}
}
Following these tutorials http://culttt.com/2013/07/08/creating-flexible-controllers-in-laravel-4-using-repositories/ http://culttt.com/2013/07/15/how-to-structure-testable-controllers-in-laravel-4/ I use repositories to avoid coupling my DB to the tests. So I have these 2 extra classes:
<?php
namespace Dumminvoicing\Storage\Project;
use Project;
class EloquentProjectRepository implements ProjectRepository
{
public function all()
{
return Project::all();
}
public function find($id)
{
return Project::find($id);
}
}
<?php
namespace Dumminvoicing\Storage\Project;
interface ProjectRepository
{
public function all();
public function find($id);
}
When I run the test, I get this error:
There was 1 error:
1) ProjectControllerTest::testIndex
Mockery\Exception\InvalidCountException: Method all() from Mockery_2143809533_Dumminvoicing_Storage_Project_ProjectRepositoryInterface should be called
exactly 1 times but called 0 times.
The index method of the controller works fine in the browser:
use Dumminvoicing\Storage\Project\ProjectRepository as Project;
class ProjectsController extends \BaseController
{
protected $project;
public function __construct(Project $project)
{
$this->project = $project;
$this->beforeFilter('auth');
}
}
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
$data['projects'] = $this->project->all();
return View::make('projects.index', $data) ;
}
So why is it failing in the test? Why is "all" not being called?
If the user has to be authenticated to hit the index method, you need to authenticate each test.
The all isn't getting called because the user is being redirected.
Create an authentication method that you can call each time you need to authenticate the request.
To see why the test failing, dump out the response before you do the assert.
Edit
The problem is you've mocked Dumminvoicing\Storage\Project\ProjectRepositoryInterface but it should be Dumminvoicing\Storage\Project\ProjectRepository.
If you correct the namespace and add $this->mock->shouldReceive('all')->once(); to the testAllowedWhenLogged() method your tests will pass correctly.