Non-Static Method should not be called Statically - php

I am using a repository pattern and am trying to establish relationships between models. When I try to run the store() method (in the controller) which is trying to use the user() method (which establishes the relationship with the Party model), I get the following error message:
Non-static method Party::user() should not be called statically, assuming $this from incompatible context
I don't understand why I get this error when I try to run the user() relationship method, but all of the other methods (including $this->party->all(), $this->party->create($data)), work just fine.
Here is the relevant code:
// PartiesController.php
public function __construct(Party $party){
$this->party = $party
}
public function store(){
$data = Input::all();
$user = Sentry::getUser();
$this->party->user()->create($data);
}
// Party.php
class Party extends Eloquent{
public function user(){
return $this->belongsTo('User');
}
}
// User.php
use Cartalyst\Sentry\Users\Eloquent\User as SentryUserModel;
class User extends SentryUserModel implements UserInterface, RemindableInterface {
public function party(){
return $this->hasMany('Party');
}
}
// PartyRepository.php
namespace repositories\Party;
interface PartyRepository{
public function all();
public function findByID($id);
public function create($input);
public function user();
}
// EloquentPartyRepository.php
namespace repositories\Party;
use Party;
class EloquentPartyRepository implements PartyRepository{
public function all(){
return Party::all();
}
public function create($input){
return Party::create($input);
}
public function user(){
return Party::user();
}
}

The issue is because you are calling a non-static method in a static context. You may be used to seeing the way Laravel does a lot of this (e.g. User::find() and the like). These, in reality though, are not static calls (a class instance is actually being resolved behind the scenes and the find() method invoked on that instance).
In your case, it is just a plain static method call. PHP would allow this, except for the fact that in the method you are referencing $this and PHP doesn't know what to do with it. Static method calls, by definition, have no knowledge of any instances of a class.
My advice would be to inject an instance of your Model class into your repository's constructor, something like this:
//Class: EloquentPartyRepository
public function __construct(Party $party)
{
$this->party = $party;
}
public function user($partyId)
{
return $this->party->find($partyId)->user();
}
The Party instance you send to the constructor should not be a record from the database, just an empty instance of Party (i.e. new Party()), though I believe if you just add it to the constructor, the IoC should be able to leverage dependency injection and provide you with an instance.
An equivalent implementation is here, that adds a byId method:
//Class: EloquentPartyRepository
public function __construct(Party $party)
{
$this->party = $party;
}
public function byId($partyId)
{
return $this->party->find($partyId);
}
public function user($partyId)
{
if($party = $this->byId($partyId)) {
return $party->user();
}
return null;
}

I have solved the problem. Thank you #watcher and #deczo for your feedback. Both were very helpful and relevant to this error message.
In the end, I only needed to change one line. I had the sequence of method calls out of order in the store() function. Here is the relevant code.
// PartiesController.php
public function store(){
$data = Input::all();
$user = Sentry::getUser();
$user->party()->create($data);
}
In my case, to remove the non-static error and to properly insert the User model into the Party model, I only had to make the aforementioned change.
I referred to http://laravel.com/docs/eloquent/#inserting-related-models for the appropriate sequence.

Related

Laravel 7: Service provider bind a class with a constructor parameter model

I've created a Service Provider with a class that has a model passed into the constructor.
The model needs to be a specific record based off the $id taking from the URL eg /path/{$id}
How can I use the requested model in the Service Provider?
An option is to pass the model into the execute method but for now I'll need to pass it into the construct.
MyController
class MyController {
public function show(MyClass $myClass, $id)
{
$model = MyModel::find($id);
return $myClass->execute();
}
}
MyClass
class MyClass
{
$private $myModel;
public function __construct(MyModel $myModel)
{
$this->myModel = $myModel;
}
public function execute()
{
//do something fun with $this->myModel
return $theFunStuff;
}
}
MyServiceProvider
public function register()
{
$this->app->singleton(MyClass::class, function ($app) {
return new MyClass(/* How can I use $myModel? */);
});
}
I don't see any value / reason to use a singleton here.
The service provider registers the singleton before your route is resolved, so there is no way to pass the $model from the controller into the register method. I would remove the service provider and do the following:
From the docs:
If some of your class' dependencies are not resolvable via the
container, you may inject them by passing them as an associative array
into the makeWith method:
$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);
So in your case something like this:
public function show($id)
{
return app()->makeWith(MyClass::class, ['myModel' => MyModel::find($id)])->execute();
}
Or shorter with the help of route model binding:
public function show(MyModel $myModel)
{
return app()->makeWith(MyClass::class, compact('myModel'))->execute();
}
Note that the argument names passed to makeWith have to match the parameter names in the class constructor.

Laravel attach returns undefined method for belongsToMany relationship

I would like to create a question which has many surveys. In the questions Model:
public function surveys()
{
return $this->belongsToMany(Survey::class, 'survey__surveyquestions');
}
And in the controller when saving a new question:
private $questions;
public function __construct(QuestionsRepository $questions)
{
parent::__construct();
$this->questions = $questions;
}
public function store(Request $request)
{
$this->questions->create($request->all());
$this->questions->surveys()->attach($request->surveys);
return redirect()->route('admin.survey.questions.index')
->withSuccess(trans('core::core.messages.resource created', ['name' => trans('survey::questions.title.questions')]));
}
But I get the following error when it gets to the attach line:
(1/1) FatalErrorException Call to undefined method
Modules\Survey\Repositories\Eloquent\EloquentQuestionsRepository::surveys()
I notice the error mentions EloquentQuestionsRepository but I have added no methods in there so it's just an empty class:
class EloquentQuestionsRepository extends EloquentBaseRepository implements QuestionsRepository
{
}
QuestionRepository:
interface QuestionsRepository extends BaseRepository
{
}
As explained in the response to the main post - the constructor resolves the QuestionsRepository to instance of EloquentQuestionsRepository, which by the look of it is not what the store method needs.
What I would probably do is to make call to create method directly on the model and remove constructor all together - that is unless you need the instance of QuestionsRepository anywhere else in your controller:
public function store(Request $request)
{
$question = Question::create($request->all());
$question->surveys()->attach($request->surveys);
...
}
Also - I'm not sure passing $request->all() is the best thing to do - I'd probably use $request->only(...) or $request->all(...) specifying which items you want to get from the request rather than passing everything from the request to the create method.
On the other note - you could also use Form Request, which would validate data for your before passing it to the store method.
https://laravel.com/docs/5.5/validation#form-request-validation

Laravel - Unable to override the create method for a model

When overriding the base Laravel model create method, the application fails. No errors are sent back to the browser, and the server logs are empty. The strange thing, it works just fine without the override. Not sure what I'm doing wrong.
Simplified controller function:
public function save(Request $request, $id = 0) {
$myModel = MyModel::find($id);
$data = $request->all();
if (!$myModel) // Create a new row
{
$myModel = MyModel::create($data);
}
// ...
}
This works fine until I add this function to the model:
class MyModel extends Model
{
// ...
public static function create($attributes = [])
{
return parent::create($attributes);
}
// ...
}
Just to clarify, I'm not looking for a different way to implement this. I just need to know why the parent call is failing for me.
Here's some server info...
Server OS: Windows 7 (personal PC with XAMPP)
Laravel version 5.4.15
PHP version 5.6.30
The explanation is that the public static function create() is not defined in the Illuminate\Database\Eloquent\Model class.
Is handled as dynamic method call, that is is handled by calling the function:
public static function __callStatic($method, $parameters)
In the Illuminate\Database\Eloquent\Model class.
At the end the create() function is defined in the Illuminate\Database\Query\Builder class.
That is the reason you cant override it in your Model and call parent::create()
IMHO I have not tested entirely, you could try with:
public static function create($attributes)
{
return (new static)->newQuery()->create($attributes);
}
About errors, you should make sure you have enabled log_errors on your server. About the error, when you extend class and want to override method, it should have same signature, so it should be:
public static function create(array $attributes = [])
{
// Your custom code goes here
return parent::create($attributes);
}
(notice array type hinting). Obviously I assume you are going to add something more in this function :)

Laravel 5: Type-hinting a FormRequest class inside a controller that extends from BaseController

I have a BaseController that provides the foundation for most HTTP methods for my API server, e.g. the store method:
BaseController.php
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
I then extend on this BaseController in a more specific controller, such as the UserController, like so:
UserController.php
class UserController extends BaseController {
public function __construct(UserRepository $repo)
{
$this->repo = $repo;
}
}
This works great. However, I now want to extend UserController to inject Laravel 5's new FormRequest class, which takes care of things like validation and authentication for the User resource. I would like to do this like so, by overwriting the store method and using Laravel's type hint dependency injection for its Form Request class.
UserController.php
public function store(UserFormRequest $request)
{
return parent::store($request);
}
Where the UserFormRequest extends from Request, which itself extends from FormRequest:
UserFormRequest.php
class UserFormRequest extends Request {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required',
'email' => 'required'
];
}
}
The problem is that the BaseController requires a Illuminate\Http\Request object whereas I pass a UserFormRequest object. Therefore I get this error:
in UserController.php line 6
at HandleExceptions->handleError('2048', 'Declaration of Bloomon\Bloomapi3\Repositories\User\UserController::store() should be compatible with Bloomon\Bloomapi3\Http\Controllers\BaseController::store(Illuminate\Http\Request $request)', '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php', '6', array('file' => '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php')) in UserController.php line 6
So, how can I type hint inject the UserFormRequest while still adhering to the BaseController's Request requirement? I cannot force the BaseController to require a UserFormRequest, because it should work for any resource.
I could use an interface like RepositoryFormRequest in both the BaseController and the UserController, but then the problem is that Laravel no longer injects the UserFormController through its type hinting dependency injection.
In contrast to many 'real' object oriented languages, this kind of type hinting design in overridden methods is just not possible in PHP, see:
class X {}
class Y extends X {}
class A {
function a(X $x) {}
}
class B extends A {
function a(Y $y) {} // error! Methods with the same name must be compatible with the parent method, this includes the typehints
}
This produces the same kind of error as your code. I would just not put a store() method in your BaseController. If you feel that you are repeating code, consider introducing for example a service class or maybe a trait.
Using a service class
Below a solution that makes use of an extra service class. This might be overkill for your situation. But if you add more functionality to the StoringServices store() method (like validation), it could be useful. You can also add more methods to the StoringService like destroy(), update(), create(), but then you probably want to name the service differently.
class StoringService {
private $repo;
public function __construct(Repository $repo)
{
$this->repo = $repo;
}
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
class UserController {
// ... other code (including member variable $repo)
public function store(UserRequest $request)
{
$service = new StoringService($this->repo); // Or put this in your BaseController's constructor and make $service a member variable
return $service->store($request);
}
}
Using a trait
You can also use a trait, but you have to rename the trait's store() method then:
trait StoringTrait {
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
class UserController {
use {
StoringTrait::store as baseStore;
}
// ... other code (including member variable $repo)
public function store(UserRequest $request)
{
return $this->baseStore($request);
}
}
The advantage of this solution is that if you do not have to add extra functionality to the store() method, you can just use the trait without renaming and you do not have to write an extra store() method.
Using inheritance
In my opinion, inheritance is not so suitable for the kind of code reuse that you need here, at least not in PHP. But if you want to only use inheritance for this code reuse problem, give the store() method in your BaseController another name, make sure that all classes have their own store() method and call the method in the BaseController. Something like this:
BaseController.php
/**
* Store a newly created resource in storage.
*
* #return Response
*/
protected function createResource(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
UserController.php
public function store(UserFormRequest $request)
{
return $this->createResource($request);
}
You can move your logic from BaseController to trait, service, facade.
You can not override existing function and force it to use different type of argument, it would break stuff. For example, if you later would write this:
function foo(BaseController $baseController, Request $request) {
$baseController->store($request);
}
It would break with your UserController and OtherRequest because UserController expects UserController, not OtherRequest (which extends Request and is valid argument from foo() perspective).
As others have mentioned, you cannot do what you want to do for a host of reasons. As mentioned, you can solve this problem with traits or similar. I am presenting an alternative approach.
At a guess, it sounds like you are trying to follow the naming convention put forth by Laravel's RESTful Resource Controllers, which is kind of forcing you to use a particular method on a controller, in this case, store.
Looking at the source of ResourceRegistrar.php we can see that in the getResourceMethods method, Laravel does either a diff or intersect with the options array you pass in and against the default values. However, the those defaults are protected, and include store.
What this means is that you can't pass anything to Route::resource to force some override of the route names. So let's rule that out.
A simpler approach would be to simply set up a different method just for this route. This can be achieved by doing:
Route::post('user/save', 'UserController#save');
Route::resource('users', 'UserController');
Note: As per the documentation, the custom routes must come prior to the Route::resource call.
The declaration of UserController::store() should be compatible with BaseController::store(), which means (among other things) that the given parameters for both the BaseController as well as UserController should be exactly the same.
You actually cán force the BaseController to require a UserFormRequest, it's not the prettiest solution, but it works.
By overwriting there is no way you can replace Request with UserFormRequest, so why not use both? Giving both methods an optional parameter for injecting the UserFormRequest object. Which would result in:
BaseController.php
class BaseController {
public function store(Request $request, UserFormRequest $userFormRequest = null)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
UserController.php
class UserController extends BaseController {
public function __construct(UserRepository $repo)
{
$this->repo = $repo;
}
public function store(UserFormRequest $request, UserFormRequest $userFormRequest = null)
{
return parent::store($request);
}
}
This way you can ignore the parameter when using BaseController::store() and inject it when using UserController::store().
The easiest and cleanest way I found to circumvent that problem was to prefix the parent methods with an underscore. For example:
BaseController:
_store(Request $request) { ... }
_update(Request $request) { ... }
UserController:
store(UserFormRequest $request) { return parent::_store($request); }
update(UserFormRequest $request) { return parent::_update($request); }
I feel like creating service providers is an overkill. What we're trying to circumvent here is not the Liskov substitution principle, but simply the lack of proper PHP reflection. Type-hinting methods is, in itself, a hack after all.
This will force you to manually implement a store and update in every child controller. I don't know if that's bothersome for your design, but in mine, I use custom requests for each controller, so I had to do it anyway.

Laravel 4 setting up model using the IoC container

I recently watched this video and wanted to change my Laravel controllers so that they had their dependencies managed with Laravel's IoC container. The video talks about creating an interface for a Model and then implementing that interface for the specific data source used.
My question is: when implementing the interface with a class that extends Eloquent and binding that class to the controller so that it is accessible from $this->model, should I also create interfaces and implementations for the Eloquent models which may be returned when calling methods such as $this->model->find($id)? Should there be different classes for the Model and the ModelRepository?
Put it another way: how do I do new Model when my model is in $this->model.
Generally, yes, people doing that pattern (the repository pattern) have an interface which have some methods defined that your app will use:
interface SomethingInterface {
public function find($id);
public function all();
public function paged($offset, $limit);
}
Then you create an implementation of this. If you're using Eloquent, then you can make an Eloquent implementation
use Illuminate\Database\Model;
class EloquentSomething {
protected $something;
public function __construct(Model $something)
{
$this->something = $something;
}
public function find($id)
{
return $this->something->find($id);
}
public function all() { ... }
public function paged($offset, $limit) { ... }
}
Then you make a service provider to put it all together, and add it into app/config/app.php.
use Something; // Eloquent Model
use Namespace\Path\To\EloquentSomething;
use Illuminate\Support\ServiceProvider;
class RepoServiceProvider extends ServiceProvider {
public function register()
{
$app = $this->app;
$app->bind('Namespace/Path/To/SomethingInterface', function()
{
return new EloquentSomething( new Something );
});
}
}
Finally, your controller can use that interface as a type hint:
use Namespace/Path/To/SomethingInterface;
class SomethingController extends BaseController {
protected $something;
public function __construct(SomethingInterface $something)
{
$this->something = $something;
}
public function home() { return $this->something->paged(0, 10); }
}
That should be it. Apologies on any errors, this isn't tested, but is something I do a lot.
Downsides:
More code :D
Upsides:
Able to switch out implementations (instead of EloquentSomething, can use ArraySomething, MongoSomething, whatever), without changing your controller code or any code that uses an implementation of your interface.
Testable - you can mock your Eloquent class and test the repository, or mock your constructor dependency and test your controller
Re-usable - you can App::make() to get the concrete EloquentSomething anywhere in your app and re-use the Something repository anywhere in your code
Repository is a good place to add additional logic, like a layer of cacheing, or even validation rules. Stock mucking about in your controllers.
Finally:, since I likely typed all that out and STILL DIDN'T ANSWER YOUR QUESTION (wtf?!), you can get a new instance of the model using $this->model. Here's an example for creating a new Something:
// Interface:
public function create(array $data);
// EloquentSomething:
public function create(array $data)
{
$something = this->something->newInstance();
// Continue on with creation logic
}
Key is this method, newInstance().
I've used $newModel = $this->model and it's worked for me.

Categories