I want to separate data from source of the data. One class for database interaction and class for data manipulation. But my approach violates LSP: preconditions cannot be strengthened in a subtype and raises strict error: Declaration of DataRepositoryItem::save() should be compatible with DataRepositoryAbstract::save(DataAbstract $data)
class DataAbstract {
}
class DataItem extends DataAbstract {
}
class DataObject extends DataAbstract {
}
abstract class DataRepositoryAbstract {
/** #return DataAbstract */
public function loadOne(){}
/** #return DataAbstract[] */
public function loadAll(){}
public function save(DataAbstract $data){}
}
class DataRepositoryItem extends DataRepositoryAbstract {
/** #return DataItem */
public function loadOne(){}
/** #return DataItem[] */
public function loadAll(){}
public function save(DataItem $data) {} // <--- violates LSP, how to avoid it?
}
class DataRepositoryObject extends DataRepositoryAbstract {
/** #return DataObject */
public function loadOne(){}
/** #return DataObject[] */
public function loadAll(){}
public function save(DataObject $data) {} // <--- violates LSP, how to avoid it?
}
How to recombine the code to fit LSP?
Update: Ok, I could rewrite methods.
class DataRepositoryItem extends DataRepositoryAbstract {
/** #return DataItem */
public function loadOne(){}
/** #return DataItem[] */
public function loadAll(){}
public function save(DataAbstract $data) {
assert($date instanceof DataItem);
//...
}
}
Works in PHP, but still violates LSP. How to avoid it?
If your language would support generics then the problem would be quite simple to solve:
public interface Repository<T> {
public void save(T data);
}
public class DataItemRepository implements Repository<DataItem> {...}
If you don't have generics then you could simply avoid trying to have a generic repository in the first place, which does more harm than good. Is there really any client code that should depend on DataRepositoryAbstract rather than a concrete repository class? If not then why forcing a useless abstraction in the design?
public interface DataItemRepository {
public DataItem loadOne();
public DataItem[] loadAll();
public void save(DataItem dataItem);
}
public class SqlDataItemRepository implements DataItemRepository {
...
}
public interface OtherRepository {
public Other loadOne();
public Other[] loadAll();
public void save(Other other);
}
Now, if somehow all save operations can be handled in a generic way you could still implement a RepositoryBase class which is extended by all repositories without violating the LSP.
public abstract class RepositoryBase {
protected genericSave(DataAbstract data) { ... }
}
public class SqlDataItemRepository extends RepositoryBase implements DataItemRepository {
public void save(DataItem item) {
genericSave(item);
}
}
However at that point you should probably use composition over inheritance by having your repositories collaborate with a GenericRepository instance:
public void save(DataItem item) {
genericRepository.save(item);
}
PS: Please note that none of the code is actual PHP code. I'm not a PHP programmer and haven't looked-up the syntax, but you should figure it out.
In any event, your inheritance hierarchy violates LSP principle, because save method and its use depends on a concrete class from an incoming object. Even if you remove a type assertion in the save method then you will not be able to use the child class DataRepositoryItem instead of parent class DataRepositoryAbstract because saving a DataItem entity differs from saving a DataAbstact entity. Let's imagine the following case of using DataRepositoryItem instead of DataRepositoryAbstract:
$repository = new DataRepositoryItem();
$entity = new DataAbstract()
// It causes incorrect behavior in DataRepositoryItem
$repository->save($entity);
We can conclude: There is no point in declaring a save method in DataRepositoryAbstract. Save method should be declared only in the concrete repository class.
abstract class DataRepositoryAbstract
{
/**
* #return DataAbstract
*/
public function loadOne(){}
/**
* #return DataAbstract[]
*/
public function loadAll(){}
}
class DataRepositoryItem extends DataRepositoryAbstract
{
/**
* #return DataItem
*/
public function loadOne(){}
/**
* #return DataItem[]
*/
public function loadAll(){}
/**
* #param DataItem
*/
public function save(DataItem $data) {}
}
class DataRepositoryObject extends DataRepositoryAbstract
{
/**
* #return DataObject
*/
public function loadOne(){}
/**
* #return DataObject[]
*/
public function loadAll(){}
/**
* #param DataObject
*/
public function save(DataObject $data) {}
}
This inheritance hierarchy provides an ability to read data from DataRepositoryObject and DataRepositoryItem the same as from DataRepositoryAbstract.
But let me ask: Where and how do you use DataRepositoryAbstract class? I sure you use it to ensure contact between concrete repository class and another code. It means that your DataRepositoryAbstract class doesn't implement any functionality, that isn't used functionally and it is a pure interface. If my assumption is valid then you should use an interface instead of an abstract class
Interfaces:
interface BaseDataRepositoryInterface
{
/**
* #return DataAbstract
*/
public function loadOne();
/**
* #return DataAbstract[]
*/
public function loadAll();
}
interface DataRepositoryItemInterface extends BaseDataRepositoryInterface
{
/**
* #return DataItem
*/
public function loadOne();
/**
* #return DataItem[]
*/
public function loadAll();
/**
* #param DataItem $data
*/
public function save(DataItem $data);
}
interface DataRepositoryObjectInterface extends BaseDataRepositoryInterface
{
/**
* #return DataObject
*/
public function loadOne();
/**
* #return DataObject[]
*/
public function loadAll();
/**
* #param DataObject $data
*/
public function save(DataObject $data);
}
Concrete implementation:
class DataRepositoryItem implements DataRepositoryItemInterface
{
public function loadOne()
{
//...
}
public function loadAll()
{
//...
}
public function save(DataItem $data)
{
//...
}
}
Related
I have a abstract class Foo and a abstract builder FooBuilder
abstract class Foo {
}
abstract class FooBuilder {
protected Foo $model;
/**
* Return the class instance
*
* #return Model //What is the correct return type??
*/
public function get()
{
return $this->model;
}
}
I want to use the method get() in my child Builders and the IDE detect that the return type is the child class, not the abstract Foo.
class Bar extends Foo {
}
abstract class BarBuilder {
public function __construct()
{
$this->model = new Bar();
}
}
$barBuilder = BarBuilder();
$bar = $barBuilder->get(); //The type is "Bar", but IDE thinks is "Foo"
Is there any way of returning a static type of a attribute not the class in PHPDoc? Something like #return static($this->model)?
An example is what Laravel's Eloquent does in SomeModel::find(). The IDE knows that the type can be SomeModel. But the #return only have Model.
You should probably use Foo as the return type in your example; but for fun, you can use static return type to determine child instance like below
class Foo
{
/**
* #return string
*/
public function Iam(): string
{
return "hello";
}
}
class Helper
{
/**
* Return the class instance
*
* #return static
*/
public static function get(): static
{
return new self();
}
}
class FooBuilder extends helper
{
protected Foo $model;
public function mememe()
{
echo "I am a method";
}
}
class FooBuilder2 extends helper
{
protected Foo $model;
public function xray()
{
echo "I am a method";
}
}
$test = FooBuilder::get();
$test->mememe();
$test2 = FooBuilder2::get();
$test2->xray();
Using generics in docblock does help PhpStorm(mine is 2021.2.2):
class Foo
{
}
/**
* #template T of Foo
*/
class FooBuilder
{
/**
* #var T
*/
protected Foo $model;
/**
* #return T
*/
public function get(): Foo
{
return $this->model;
}
}
class Child extends Foo
{
public function testCompletion(){}
}
/**
* #extends FooBuilder<Child>
*/
class ChildBuilder extends FooBuilder
{
}
class AnotherChild extends Foo
{
public function otherMethod(){}
}
/**
* #extends FooBuilder<AnotherChild>
*/
class AnotherChildBuilder extends FooBuilder
{
}
$childBuilder = new ChildBuilder();
$childBuilder->get()->testCompletion(); // testCompletion is suggested
$anotherChildBuilder = new AnotherChildBuilder();
$anotherChildBuilder->get()->otherMethod(); // otherMethod is suggested
For example we have the following abstract class
<?php
class AbstractClass {
public function setParam(): AbstractClass {}
}
class ConcreteClass extends AbstractClass {
public function test():void {}
}
When you'll try to use it like this
<?php
(new ConcreteClass())->setParam()->test();
Then after setParam we will see only setParam method, because setParam returns AbstractClass. I tried to mark setParam inside AbsractClass with PHP-doc #return self, but it doesn't work.
Are there any solutions of this problem?
To solve this problem you can use #return static PHP-doc attribute
<?php
class A {
/** #return static */
public function methodA(): A;
}
class B {
/** #return static */
public function methodB(): B;
}
(new B())->methodB()->methodA()->methodB();
Everything in this example will be highlighted correctly.
I am using Laravel 5.0 to create phpunit test alongside the actual model.
I get errors in phpunit tests but no errors when controller calls the model and it returned the desired data.
sample.php
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class sample extends Model {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'sample';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['id','username','details','image'];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
public static function test()
{
return "Returned Text.";
}
public static function gettest()
{
return self::test();
}
public static function getItem()
{
return self::orderBy('username','asc')->get();
}
public static function findItem($id)
{
return self::find($id);
}
}
SampleTest.php
<?php namespace App;
use Mockery as m;
class SampleTest extends \PHPUnit_Framework_TestCase {
protected function setUp()
{
$this->mock = m::mock('App\sample')->makePartial();
}
protected function tearDown()
{
m::close();
}
/** #test */
public function should_return_string()
{
$response = $this->mock->test();
var_dump("test() returns :".$response);
}
/** #test */
public function should_return_string_from_test_function()
{
$response = $this->mock->gettest();
var_dump("gettest() returns :".$response);
}
/** #test */
public function should_return_mocked_data()
{
$this->mock->shouldReceive('test')->andReturn('Return Mocked Data');
$response = $this->mock->gettest();
var_dump("gettest() returns :".$response);
}
/** #test */
public function should_return_some_data_using_this_mock()
{
$this->mock->shouldReceive('get')->andReturn('hello');
$response = $this->mock->getItem();
}
}
Problem
When I use controller to call the model, it returned the desired data.
When I run phpunit on command prompt:-
test function is not mocked properly as it still returns the original string
getItem and findItem functions return an error saying
1) App\SampleTest::should_return_some_data_using_this_mock
BadMethodCallException: Static method Mockery_0_App_sample::getItem()
does not exist on this mock object
Question
How can I mock the function properly? Why it is saying the error code as shown above? Where was I doing it wrong?
Any help will be much appreciated.
Note: Test assertions is removed and replaced with var_dump to see the output on the command prompt.
I am using the User class included with laravel 4. I am trying to store a new question that belongs to the user and the user needs to be logged in to create. when I call the questions controller action store I get the following error
Class User contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Illuminate\Auth\UserInterface::getAuthPassword, Illuminate\Auth\Reminders\RemindableInterface::getReminderEmail)
I have read a bit on abstract methods in php and while I don't completely understand them the error itself gives two solutions to the problem, declare the class abstract of implement the remaing methods. I am guessing that since this is the model class that ships with laravel that the correct solution is not to change its declaration to abstract but to implement the remaining methods. How do I do this correctly in this case and going forward?
User Model
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends BaseModel implements UserInterface, RemindableInterface {
protected $guarded = [];
public static $rules = array(
'username' => 'required|unique:users|alpha_dash|min:4',
'password' => 'required|alpha_num|between:4,8|confirmed',
'password_confirmation'=>'required|alpha_num|between:4,8'
);
public function Questions($value='')
{
return $this->hasMany('Question');
}
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password');
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->getKey();
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
return $this->email;
}
}
Questions Controller
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function postStore()
{
$validation = Question::validate(Input::all());
if($validation->passes()) {
Question::create(array(
'question'=>Input::get('question'),
'user_id'=>Auth::user()->id
));
return Redirect::Route('home')
->with('message', 'Your question has been posted.');
} else {
return Redirect::to('user/register')->withErrors($validation)
->withInput();
}
}
edit 1: The error message includes '(Illuminate\Auth\UserInterface::getAuthPassword, Illuminate\Auth\Reminders\RemindableInterface::getReminderEmail)' these two methods are in my user.php as publice functions as you can see above, so do I need to do something else to 'implement' them?
edit 2:
Laravel Src UserInterface Class
<?php namespace Illuminate\Auth;
interface UserInterface {
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier();
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword();
}
laravel src RemindableInterface class
<?php namespace Illuminate\Auth\Reminders;
interface RemindableInterface {
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail();
}
edit 3:
php.ini related to error reporting
; error_reporting
; Default Value: E_ALL & ~E_NOTICE
; Development Value: E_ALL | E_STRICT
; Production Value: E_ALL & ~E_DEPRECATED
error_reporting = E_ALL
; Eval the expression with current error_reporting(). Set to true if you want
; error_reporting(0) around the eval().
; http://php.net/assert.quiet-eval
;assert.quiet_eval = 0
basemodel class
<?php
class Basemodel extends Eloquent {
public static function validate($data) {
return Validator::make($data, static::$rules);
}
}
?>
edit 4;
Adding correct model class as it was when giving the error and how it is now with the fix
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class Question extends BaseModel implements UserInterface, RemindableInterface {
protected $guarded = [];
public static $rules = array(
'questions'=>'required|min:10|max:255',
//'solved'=>'in:0,1',
);
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'questions';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('');
/**
* Get the unique identifier for the question.
*
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->getKey();
}
public function user()
{
return $this->belongsTo('User');
}
}
add this to fix
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
return $this->email;
}
Perhaps it's easiest to answer this with an example. Say I have the following classes:
abstract class ClassB {
public abstract function foo();
}
class ClassA extends ClassB {}
$a = new ClassA();
Running this code will result in the following error:
Fatal error: Class ClassA contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (ClassB::foo)
This means I'm missing an implementation of foo() (defined in ClassB) in ClassA. Abstract methods can only be defined in abstract classes, and mean that any non-abstract derived class must expose a full implementation - which ClassA doesn't in this case. The above example can be fixed by changing ClassA to
class ClassA extends ClassB {
// implementation of abstract ClassB::foo().
public function foo() {
echo 'Hello!';
}
}
Back to your example. Your User class extends BaseModel. Depending on whether BaseModel extends another abstract class, it will contain two methods defined as abstract that your User class is missing. You need to find these methods - my error message explicitly told me what I was missing - and implement them in User.
I am implementing a repository pattern in Laravel, and it seems to be very tedious. For example, let's say I have products then I have to create a ProductRepository interface then a ProductRepository class that implements that interface, now I have some very generic methods on the ProductRepository like:
retrieveAll
store
update
delete
And now I have to do the same thing for ingredients. It would be nice if I could simply create a ModelRepository interface with all those generic methods and implement it by passing a generic data type (namely the model), something similar to Java Generics:
<?php
interface ModelRepositoryInterface<T> {
function retrieveAll(): Collection<T>;
function store(T $item);
function update(int $id, T $data);
function delete(int $id);
}
But since php doesn't support generics how can I achieve this simplicity?
You can create a RepositoryServiceProvider to bind your repository interfaces to actual classes.
You can create a abstract Repository class with retrieveAll, store, update, delete and extend your Repositories and implement the interface. I have included below example with magic functions to be able to eloquent methods if I don't have any customization.
The below is not tested but its just to get the idea.
<?php
namespace App\Repositories;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
abstract class AbstractRepository implements RepositoryInterface
{
/**
* #var Builder|Model
*/
protected $model;
/**
* #return mixed
*/
public function getModel()
{
return $this->model;
}
/**
* #param array $columns
* #return \Illuminate\Database\Eloquent\Collection|Model[]
*/
public function all($columns = ['*'])
{
return $this->model->all($columns);
}
/**
* #param $name
* #param $arguments
* #return mixed
*/
public function __call($name, $arguments)
{
return $this->model->{$name}($arguments);
}
}
OrderRepository
<?php
namespace App\Repositories;
use App\Models\Order;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
class OrderRepository extends AbstractRepository implements OrderRepositoryInterface
{
/**
* OrderRepository constructor.
* #param Order $model
*/
public function __construct(Order $model)
{
$this->model = $model;
}
public function countPaid(): int
{
return $this->model->paid()->count();
}
/**
* #return int
*/
public function countReady(): int
{
return $this->model->ready()->count();
}
/**
* #return int
*/
public function countCancelled(): int
{
return $this->model->cancelled()->count();
}
}
OrderRepositoryInterface
<?php
namespace App\Repositories;
interface OrderRepositoryInterface
{
}
RepositoryServiceProvider
<?php
namespace App\Providers;
use App\Repositories\OrderRepository;
use App\Repositories\OrderRepositoryInterface;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(OrderRepositoryInterface::class, OrderRepository::class);
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
}
}
RepositoryInterface
<?php
namespace App\Repositories;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
interface RepositoryInterface
{
function retrieveAll(): Collection;
function store(Model $item);
function update(int $id, Model $data);
function delete(int $id);
}