Model Contracts/Interfaces in Laravel 5 - php

What is the most Laravel way to impose an interface or contract on a factory mock of a model?
For example, let's say I have a model Dog. I use a factory to mock it, and I write a test for it. I want to make sure that if I mock an instance that does not conform to the contract/interface, an error is thrown.
\App\Dog.php:
class Dog extends Model
{
protected $table = 'dogs';
protected $fillable = ['name', 'breed' ]; // Dogs must have both of these
}
database/factories/ModelFactory.php
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name // no mention of breed, which is required
];
});
tests/DogTest.php
class RoadshowUnitTest extends TestCase
{
/** #test **/
public function i_should_be_able_to_make_a_dog()
{
$dog = factory(App\Roadshow::class, 1)->make();
// ... internal type assertions go here
}
}
When I run this test, I should get some kind of error; the factory should not be able to produce a Dog at all, since it is missing an important field. How do I impose the requirement on the Model, and anything that uses it (like the factory)?

The model factory just creates a new instance of the model object you pass it and assigns values to the attributes that you specify (in this case, just the 'name' attribute). Just as you can do $user = new User; and instantiate an empty user object, your factory can have as few attributes as you want when you run make.
To ensure that a persisted instance of that model class has the appropriate data attributes, you can: add validation logic that validates request data (in a custom request class or in your controller), or apply constraints to your database table that holds the model data. Ideally you would do both.
Using the make command is the same as doing new Model, meaning the data is not saved to the database yet (if you want it persisted you can do save() after make(), or use create() in place of make()). Therefore, any database constraints you have in place will not come into play. Similarly, since it's not an HTTP request (and not calling a controller method), any validation logic you have is also not being hit.
Sorry this is kind of wordy - short answer is that the model factory itself does not validate your data.

Related

Laravel Service as Controller - working with multiples controllers

I'am a Brazilian developer, so... sorry for my limited English right away.
Well, in fact my problem is more a convention problem because until now I hadn't use services with Laravel (my apps were that simple so far).
I read about it before ask this question, but nothing helped with this specific situation. I'll try to describe in a objective way.
before that, just a comment: I know about the mistake using just controllers in these example. The ask is really about that mistake.
Well, the actual structure is:
abstract class CRUDController extends Controller {
protected function __construct($data, $validatorData) {
// store the data in a attribute
// create with Validator facade the validation and store too
}
abstract protected function createRecord();
protected function create() {
try {
// do the validation and return an Response instance with error messages
// if the data is ok, store in the database with models
// (here's where the magic takes place) in that store!
// to do that, calls the method createRecord (which is abstract)
$this->createRecord();
// return a success message in an Response instance
}
catch(\Exception $e) {
// return an Response instance with error messages
}
}
}
class UserController extends CRUDController {
public function __construct($data) {
parent::__construct($data, [
'rules' => [
// specific user code here
],
'messages' => [
// specific user code here
],
'customAttributes' => [
// specific user code here
]
]);
}
protected function createRecord() {
$user = new UserModel();
// store values here...
$user->save();
return $user;
}
}
// here's the route to consider in that example
Route::post('/user', 'WebsiteController#register');
class WebsiteController extends Controller {
private $request;
public function __construct(Request $request) {
$this->request = $request;
}
public function register() {
$user = new UserController();
$user->create($this->request);
// here's the problem: controller working with another controller
}
}
class UserAPIController extends Controller {
// use here the UserController too
}
and many other classes that extends CRUDController in the same way...
What I want
I want to create a controller (called here as CRUDController) to reuse methods like the pattern says (create, read, update and delete).
To be really objective here I'll use the create method as an example.
With the code above it seems clear the purpose? I think so... all my controllers have that code of validation equal and reusable. That's the thing.
Besides that, I want to my route of website call another controller (UserController) to store new users... but in the same way, I'll create an API that uses the same controller in the same way (with validations etc). That's the purpose of Responses in the CRUDController (I'll read them in the WebSiteController to resolve what to do, like show a view and in the other hand with the API I'll basically return the Response.
My real problem
Convention and pattern. The MVC pattern is broken here. Controller calling another controller is wrong and I know that.
I want to know what thing I should use! Services? Is that right? I see a lot (really) of examples of services but nothing like that, working with models and reusing code, etc. I never use Services but I know how to use, but I don't know if it's right to these cases.
I really hope that someone can help here and sorry once again for the mistakes with the English. Thanks a lot.
You're calling the CRUD controller a controller but it does not behave as an MVC controller. At best it's just a helper class. You could always do this:
abstract class CRUDManager {
//As you had the CRUDController
}
class UserManager extends CRUDManager {
//As you had the UserController
}
In your AppServiceProvider:
public function boot() {
$app->bind(UserManager::class, function ($app) {
return new UserManager(request()->all()); //I guess that's what you need.
});
}
Whenever you need to use it you can do:
public function register(UserManager $user) {
$user->create();
}
Now one thing to point out. It's not a good idea to initialise the request in the constructor. You should use dependency injection in controller methods. I don't even know if the request is available when the controller is being constructed (I know the session is not). The reason why I say this is that the middleware runs after the controller is constructed and therefore the request may be modified when the controller method is called.
Another note: If you did the original solution because you needed to use certain controller methods, then you can just use the corresponding traits (because the controller itself does not really have many method). I'm guessing a trait like ValidatesRequests would be one you'd need to use.
I'll answer my own question. I use a pattern called Repository Pattern to resolve the problem (or I try to use, because it's the first time using this pattern: maybe I don't use in the right way in every steps).
Files structure
Controllers
UserController.php
Models
UserModel.php
Providers
UserRepositoryServiceProvider.php
Repositories
RepositoryInterface.php
Repository.php
User
UserRepositoryInterface.php
UserRepository.php
Traits
InternalResponse.php
With that structure I did what I wanted in my question without working just with controllers.
I create a trait called InternalResponse. That trait contains a few methods that receive a transaction, validate if it's the case and then return a Response (called "internal" in my logic because the controller will read and maybe change the Response before return it in the end).
The Repository class, which is abstract (because another class must extend it to make sense to use. In this case the class UserRepository will extend...), uses the Trait mentioned.
Well, with it in mind, it's possible to know that the UserController uses the UserRepositoryInterface, that provides an object UserRepository: because the UserRepositoryServiceProvider register this with that interface.
I think there's no need to write code here to explain, because the problem is about an pattern, and these words explain well the problem (in the question) and the resolution with this answer here.
I'll write here a conclusion, I mean, the files structure with comments to explain a little bit more, to end the answer.
Conclusion: Files structure with comments
Controllers
UserController.php
// the controller uses dependency injection and call methods of
// UserRepository, read and changes the Response receveid to finally
// create the final Response, like returning a view or the response
// itself (in the case it's an API controller)
Models
UserModel.php
// an normal model
Providers
UserRepositoryServiceProvider.php
// register the UserRepositoryInterface to
// return a UserRepository object
Repositories
RepositoryInterface.php
// the main interface for the Repository
Repository.php
// the main repository. It's an abstract class.
// All the others repositories must extend that class, because
// there's no reason to use a class Repository without an Model
// to access the database... That class share methods like create,
// read, update and delete, and the methods validate and transaction
// too because uses the trait InternalResponse.
User
UserRepositoryInterface.php
// the interface for UserRepository class
UserRepository.php
// that class extend Repository and uses the UserModel
Traits
InternalResponse.php
// trait with methods like validate and transaction. the method
// validate, read and validate the data receveid for the methods
// create and update. and all the CRUD methods uses the method
// transaction to perform the data to the database and return a
// response of that action.
That's what I do and like I said before, I don't know if it's a hundred percent correct in reference to Repository Pattern.
I hope this can help someone else too.
Thanks for all.

Laravel: Access Model instance in Form Request when using Route/Model binding

I have some route/model binding set up in my project for one of my models, and that works just fine. I'm able to use my binding in my route path and accept an instance of my model as a parameter to the relevant method in my controller.
Now I'm trying to do some work with this model, so I have created a method in my controller that accepts a Form Request so I can carry out some validation.
public function edit(EditBrandRequest $request, Brand $brand)
{
// ...
Each different instance of my model can be validated differently, so I need to be able to use an instance of the model in order to build a custom set of validation rules.
Is there a way of getting the instance of the model, that is injected into the controller from the Form Request?
I have tried type-hinting the model instance in the Form Request's constructor
class EditBrandRequest extends Request
{
public function __construct(Brand $brand)
{
dd($brand);
}
I have also tried type-hinting the model instance in the Form Request's rules() method.
class EditBrandRequest extends Request
{
// ...
public function rules(Brand $brand)
{
dd($brand);
In both instances I am provided an empty/new instance of the model, rather than the instance I am expecting.
Of course, I could always get around this by not bothering with Form Requests and just generate the rules in the controller and validate manually - but I would rather do it the Laravel way if it's possible.
Thanks
You can simply access it using the binding key, so for example if you bind Brand model: $router->model('brand', '\App\Brand') you can get instance of your model with $this->brand. Here is validation rules example:
'slug' => 'required|unique:brand,slug,' . $this->brand->id,
EDIT
Sometimes you might have an input name that uses the same name as the binding key, for example, if you bind Address model as address then you have an input field address it will make Laravel confuse. For this situation you can use route() method.
'address' => 'required|unique:addresses,address,' . $this->route('address')->id,

Laravel accessing collection attributes and model attributes from reusable view

I have a view that I'm trying to re-use for two different actions to display data from the database. For one of those actions, an Eloquent collection object is passed to the view, and data is retrieved with
#foreach($buildings as $key=>$value)
{!! $value->build_name !!}
Obviously 'build_name' is a column in the table. So far simple..
Now I need this same view to display data that requires a lot of processing and it's not possible to generate an eloquent statement to pass to the view.
In order to re-use the $value->build_name code, I'm assuming I have to still pass an object (model??) to the view.
I have a Building.php Model
class Building extends Model
{
protected $fillable =[
'buildingtype_id',
'build_name',
];
and I'm thinking I could just add public $build_name; to the Building model, but then I should also add a method to set and get the $build_name. So my Building Model will now look like..
class Building extends Model
{
public $build_name;
protected $fillable =[
'buildingtype_id',
'build_name',
];
public function getBuildName () {
return $this->build_name;
}
public function setBuildName ($name) {
$this->build_name = $name;
}
And I can just create the object myself in the controller...
If I do this, is {!! $value->build_name !!} still appropiate for the view? Or should I now be using {!! $value->getBuildName() !!}
Or am I missing a key concept somewhere? I'm still new to Laravel and OOP.
Edit
I just implemented this, and it's not working. If I add the public $build_name attribute to the model, getBuildName does not return anything, however if I remove public $build_name it does... (which would break my attempting to create that object manually)
When you declare public $build_name, this will override (or more precisely, reset) any other field with the same name in the model. So, you'll have to call setBuildName() setter method before you get it.
I just implemented this, and it's not working. If I add the public $build_name attribute to the model, getBuildName does not return anything
That's because you've called the getter method before the setter, so there is nothing (null) set in the public variable $build_name.
Although you haven't quite mentioned why exactly you want to reuse the Eloquent model, but you can achieve your desired purpose with a little tweak on the model's setter methods:
class Building extends Model
{
/* ... */
public function setBuildName ($name) {
$this->build_name = $name;
return $this;
}
}
Notice returning the current object ($this) in case you would want to chain multiple setter methods in one go.
e.g.:
$model->setBuildName('some name')->setBuildHeight(400);
UPDATE
You can use the default eloquent model to serve your purpose (hence, ridding you of making a duplicate class to achieve roughly the same effect).
Now suppose you have your model Building and would like to set it's attributes manually, then, the following operation on the model is still appropriate:
$building = new App\Building();
$building->build_name = 'Some Building Name'; // you can substitute this with your setter method as well
$building->build_height = 110; // assuming you have a column named `build_height` in your model's table
Note that the only difference in what you'd be doing here is:
You DON'T declare public variables at all in the Eloquent model.
You don't call Eloquent's save() method on the model to persist the manually set data (giving it a transient behavior).
The model now is totally eligible to be passed to your view and you can access it's attributes as you would with a regular model:
<!-- in your view -->
{{ $building->build_name }}
{{ $building->build_height }}
As an alternative approach to setting your arbitrary data, you can have a single setter which accepts an array of key value data to be stored in the model (e.g. ['build_name' => 'Building Name', 'build_height' => 110]):
//in class Building
public function setData($data){
$this->build_name = $data['build_name'];
$this->build_height = $data['build_height'];
}

Can Eloquent model observer access model data that are being affected by the event?

I am trying to create an observer for an Eloquent model to work as a logger for the changes on that model. I am not sure what parameters are passed to the observer's methods from the model. If the parameters don't include an instance of the updated model, is there another way around it without having to redefine the model class and override the needed methods?
class UserObserver{
public static function saved($user){
Activity::create([
"activity-name" => "save",
"user" => $user->id
]);
}
}
I found out that the model is actually passed, my mistake was not adding user property to the fillable array in the Activity model.
usually, I get an exception when my application tries to update fields that are not included in the fillable array, but this time I didn't. anybody knows why?

Laravel extend Eloquent and 3rd party class

I'm currently working on my first PHP/Laravel 4 project, I'm developing on it a storage class to add Eloquent support to a 3rd party library.
My EloquentStorage class extends the AbstractStorage class from the library, and I make usage of most of AbstractStorage methods. Now that I want to add Eloquent support to my new EloquentStorage class, I faced the fact that PHP does not support multiple inheritance.
Is there a proper way to define an Eloquent model without extending it as:
class MyClass extends Eloquent {}
And, if not, how to deal with such situation when I need to extend 3rd party class and also extend Eloquent? Maybe using Laravel's IoC?
I think your model should extend from Eloquent, and instead be accessed through a repository. Your repository can have a $storage property, and would be responsible for calling the appropriate methods on your AbstractStorage implementation. Below is more pseudo- than actual code, but illustrates where you can plug in your implementation for an update operation.
class MyClass extends Eloquent
{
/* Normal Eloquent model implementation */
}
class MyRepository
{
protected $storage;
protected $myClass;
public function __construct(MyClass $myClass, AbstractStorage $storage)
{
$this->myClass = $myClass;
$this->storage = $storage;
}
public function update($id, $data)
{
// This is just an example operation, basically here's your chance to call
// the 3rd-party implementation. Here is pre-eloquent update, but can be
// after
$this->storage->update($id, $data);
// Use the empty Eloquent class property instance to obtain an instance of
// the requested model
$instance = $this->myClass->find($id);
// set instance properties
$instance->save();
// Example post-eloquent update
$this->storage->update($id, $data);
}
}
class MyStorage extends AbstractStorage { /* Your Storage Implementation */ }
$repo = new MyRepository(new MyClass, new MyStorage);
// Update item id 42's foo property
$repo->update(42, [ 'foo' => 'bar' ]);
A benefit to this approach is that the construction of the repository itself can be offloaded to the IoC through a Service Provider, and be injected inside of a controller / form validator etc, which means the execution will become automatic and hide the underlying complexity of the 3rd party library from the rest of the system (the repository helps keep your 3rd party abstraction from leaking).
Another benefit is that you don't need any special code in your eloquent models having anything to do with your completely unrelated 3rd party code. All of the logic is encapsulated in a single spot, and can even be shared amongst multiple models. Want to change 3rd party providers? Write a new implementation of AbstractStorage, update the service provider and you're done.
One other benefit is improved testability. Instead of statically utilizing an eloquent model directly (a la $user = User::find($id)) you would be manipulating your repository object instead ($user = $this->repo->find($id)). Since your repository can be trivially mocked and itself be tested (without also testing Eloquent or hitting the database), you can write integration tests on all of your controller routes and know the moment that changes to your codebase break your business rules.

Categories