In the following Laravel 5 Model should the findByIdAndCourseOrFail method be static?
class Section extends Model {
//should this method be static?
public function findByIdAndCourseOrFail($id, $courseId)
{
$result = $this->where('id', $id)->where('course_id', $courseId)->first();
if (!is_null($result))
{
return $result;
}
throw (new ModelNotFoundException())->setModel(Section::class);
}
}
With the controller:
class SectionsController extends Controller {
protected $sections;
public function __construct(Section $section)
{
$this->sections = $section;
}
public function foo($id, $courseId) //illustration only
{
$section = $this->sections->findOrFail($id);
$section = $this->sections->findByIdAndCourseOrFail($id, $courseId);
//would need to be non-static
$section = Section::findByIdAndCourseOrFail($id, $courseId);
//weird when compared with find above
}
On the one hand, we're not acting on a Section instance [See Note]. On the other hand, in a controller with auto-dependency injection through Laravel's service container we'd act on an instance: $sections = $this->sections-> findByIdAndCourseOrFail(7,3); and my IDE (PhpStorm) squawks if Static.
[Note]: This comment may be a misunderstanding of how Laravel Models work. For me, I would expect that find(), findOrFail() to be Class methods and thus Static as opposed to the instance that a find method would return.
I'm not sure if local scopes are meant to be used like that. But it works for me on laravel 5.2:
public function scopeFindByIdAndCourseOrFail($query, $id, $courseId)
{
$result = $query->where('id', $id)->where('course_id', $courseId)->first();
if (!is_null($result))
{
return $result;
}
throw (new ModelNotFoundException())->setModel(Section::class);
}
In the controller you can use it both ways:
$section = Section::findByIdAndCourseOrFail($id, $courseId);
Or
$model = new Section();
$section = $model->findByIdAndCourseOrFail($id, $courseId);
class Section extends Model {
public static function findByIdAndCourseOrFail($id, $courseId)
{
$result = self::where('id', $id)->where('course_id', $courseId)->first();
if (!is_null($result))
{
return $result;
}
throw (new ModelNotFoundException())->setModel(Section::class);
}
}
Personally I would make this a static method, I'm not sure if there is a "correct" answer though as either can be done. The way I kind of separate them in my mind is if I'm doing something to an instance of a model then I make it a normal public function. If I am doing something to the Collection I use a static. For example:
$person = new Person();
$person->setAdmin(true);
$person->save();
// OR
$admins = Person::getAdmins();
In the first example we have a specific instance of a Person and we are manipulating it, all code would be simply manipulating that specific instance. In the second example we are acting on the entire collection of Person and we want a collection of objects to be returned.
In your case you would have to initiate an instance of Section just to be able to use your non-static public method, like this:
$section = new Section();
$foundSection = $section->findByIdAndCourseOrFail(7,3);
So $section becomes a temporary variable that is never really used. On the other hand if you made it a static you could call it without having to do this.
$section = Section::findByIdAndCourseOrFail(7,3);
Hopefully that makes sense.
Related
I have 3 classes.
class Box{
public $item1;
public $item2;
public function __construct($item1,$item2){
$this->item = $item1;
$this->item2 = $item2;
}
public function getItem1(){
return $this->item1;
}
}
class Details{
public $stuff
public $item1;
public $item2;
public $item3;
public function __construct($stuff){
$this->stuff = $stuff
}
public function setItem1($item){
$this->item1 = $item;
}
public function setItem2($item){
$this->item2 = $item;
}
}
class Crate{
public $box;
private $stuffString = "Stuff";
public function __construct(Box $box){
$this->box = $box;
}
public function getDetails(){
$details = new Details($stuffString);
$details->setItem1($box->item1);
$details->setItem2("Detail");
return $details;
}
}
The Crate->getDetails() method returns a Details object with data from the Box object. I want to write tests for this method.
function test_get_details(){
$box = Mockery::mock(Box::class);
$box->shouldReceive('getItem1')->andReturn("BoxItem");
$crate= new Crate($box);
$details = $crate->getDetails();
$this->assertInstanceOf(Details::class,$details);
}
I create a mock of the Box class and pass it to constructor of Crate. When I call $crate->getDetails(); it should return a Details object with
$item1 = "BoxItem"
$item2 = "Detail"
$item3 = null
I know I can test this by doing for each item $this->assertEquals("BoxItem",$details->item1); etc... but is that the best way to go about it? Is there some PHPUnit tool to build up the desired Detials result and compare it
For Example
$this->assertEquals(MockDetailObject,$details)
or do I have to do a series of asserts to make sure the result is what I expect.
Note*
I know for my example this isn't a huge deal, I built it up quick to explain what I mean. But in the code I'm working on I ran into the same type of problem except the Details Object is more complex than just 3 strings.
TL;DR: create a factory and test this factory 100%.
From what I understood, your Crate class is both an entity and a factory. You could refactor Crate::getDetails by moving this creation responsibility to a factory.
This way you'll be able to unit test the creation logic only by using the "Given, When, Then" structure. Check out this post about clean tests and navigate to the "Tests should be concise and meaningful".
Having this structure will help you telling what are the inputs and outputs.
For example:
CrateDetailsFactoryTest.php
class CrateDetailFactoryTest extends TestCase
{
public function testCreateCrateDetail(): void
{
// Given
$crate = $this->givenThereIsACrate();
$boxes = $this->givenThereAreTwoRedBoxes();
// When
$crateDetail = $this->crateDetailFactory->createCrateDetail(
$crate,
$boxes
);
// Then
// (Unnecessary instanceof, if you have strict return types)
self::assertInstanceOf(Detail::class, $crateDetail);
self::assertCount(2, $crateDetail->getBoxes());
self::assertEquals(
'red',
$crateDetail->getBoxes()->first()->getColor()
);
}
}
With this your creation logic is covered; From here you can simply inject your factory where you need, and during the unit test time you just mock it away:
CrateService.php
class CrateServiceTest extends TestCase
{
private $crateDetailFactory;
private $crateService;
public function setUp(): void
{
$this->crateDetailFactory = $this->prophesize(CrateDetailFactory::class);
$this->crateService = new CrateService(
$this->crateDetailFactory->reveal()
);
}
public function testAMethodThatNeedsCrateDetails(): void
{
// Given
$crate = $this->givenIHaveACrateWithTwoBoxesInIt();
$boxes = $crate->getBoxes();
// When
$result = $this->crateService->AMethodThatNeedsCrateDetails();
// Then
$this->crateDetailFactory->createCrateDetail($crate, $boxes)
->shouldBeCalledOnce();
}
}
I hope that was useful. Cheers! :)
Using your classes above, to unit test this properly, you would have to use DI to inject \Details::class into either getDetails() or the __constructor. Then write tests for each method of each class, mocking any class dependencies/properties
class Create
{
public function getDetails(\Details $details)
}
//test.php
$mockDetails = $this->createMock(\Details::class)
->expects($this-once())
->method('item1')
->with('some_arg')
->willReturn('xyz')
$mockBox = $this-createMock(\Box::class)
......
$crate = new Create($boxMock);
$result = $crate->item1($mockDetails);
$this-assertSame('xyz', $result);
If it feels like your mocking way to much for one method, then you should consider refactoring to make the code more testable.
As far as assertions for multiple items, in PHPUnit you can use a dataprovider to pass an array of values as individual tests to one test method.
PHPUnit Docs - Data Providers
You would also write separate unit tests for the \Details::class that asserted what is passed to \Details::setItem1($item) is actually set on the item1 property. Ie.
Testing \Details::class -
//test2
public function test() {
$details = new Details('some stuff');
$details->setItem1('expected');
self::assertSame('expected', $details->item1);
}
I have the following code (simplified and details changed for this question):
class model_to_be_tested {
// an array that holds a collection of thing A
public $array_of_thing_A;
// already doing constructor injection for the data object
public __construct($data_object) {
// details here
}
public function add_new_thing_A($has_relationship) {
$thing_A = new Thing_A();
$thing_A->is_thing = true;
$thing_A->has_relationship_with_thing_B = $has_relationship;
if ($has_relationship) {
$thing_B = new Thing_B();
$thing_A->relationship_with = $thing_B;
}
$this->array_of_thing_A[] = $thing_A;
}
}
In the above example, I have to decouple the instantiation of Thing_A and Thing_B from the add_new_thing method. However, a simple constructor injection will not do for these two classes. This is because I need fresh instances of Thing_A and Thing_B every time add_new_thing is called so that Thing_A can be added to the array_of_thing_A.
How can I make this function unit testable? And more specifically for me to use mocks of Thing_A and Thing_B in testing this function in PHPUnit?
Any suggestions with code example will be appreciated.
Additionally, I would like to mention that Thing_A and Thing_B are used elsewhere in the codebase that I am working with and the code using these classes will eventually need to be unit tested. Solutions that are too localized and would cause repeated code elsewhere will not be too ideal in my situation. Thank you.
As commenter xmike mentioned, you could use the factory pattern. You would inject a factory object through the ctor as well. Then you could have a factory that provides simplified instances of your Thing_A and Thing_B.
class ThingFactory {
public function buildThingA() {
return new Thing_A(); // or MockThing_A if you go the ducktyping route
}
public function buildThingB() {
return new Thing_B();
}
}
class model_to_be_tested {
// an array that holds a collection of thing A
public $array_of_thing_A;
// you could go the typed route and have an interface for this
private $factory;
// already doing constructor injection for the data object
public __construct($data_object, $factory) {
// details here
$this->factory = $factory;
}
public function add_new_thing_A($has_relationship) {
$thing_A = $this->factory->buildThingA();
$thing_A->is_thing = true;
$thing_A->has_relationship_with_thing_B = $has_relationship;
if ($has_relationship) {
$thing_B = $this->factory->buildThingB();
$thing_A->relationship_with = $thing_B;
}
$this->array_of_thing_A[] = $thing_A;
}
}
PHP is such a strange language, you can't assign a class to a variable. But you can do it as a string. Inject ThingA and ThingB on the constructor as strings. You can call new on the string member.
class ThingA {};
class ThingB{};
class model_to_be_tested {
// an array that holds a collection of thing A
public $array_of_thing_A;
private $_thingA;
private $_thingB;
public function __construct($data_object, $thingA, $thingB) {
$this->_thingA = $thingA;
$this->_thingB = $thingB;
}
public function add_new_thing_A($has_relationship) {
$thing_A = new $this->_thingA();
if ($has_relationship) {
$thing_B = new $this->_thingB();
}
$this->array_of_thing_A[] = $thing_A;
}
}
$model = new model_to_be_tested('foo', 'ThingA', 'ThingB');
$model->add_new_thing_A(true);
There's a live version here: https://repl.it/#rmoskal/InconsequentialAnotherGermanshorthairedpointer
Or provide a static constructor for the class.
I have a class with a private variable used to store an object.
I have a function that checks first if that variable already contains an object or not; if not, it instantiates the needed object and sets it to that variable, otherwise it just returns the content of that variable.
I was wondering if the getSessionCustomer() here is an overkill/unnecessary or if it has real benefits. I simply based this on the Album tutorial by Zend, but I haven't been able to fully test it out yet to really see the advantages (or disadvantages). As far as I know it wasn't explained in the docs why this additional function was included.
class JobController extends AbstractActionController
{
private $SessionCustomer;
public function saveJobAction()
{
$SessionCustomer = $this->getSessionCustomer();
if(empty($SessionCustomer->offsetGet('customer_id'))) {
return $this->redirect()->toRoute('login');
} else {
$JobService = $this->getServiceLocator()->get('Job\Factory\JobServiceFactory');
$job_id = $JobService->saveJob();
return $this->redirect()->toUrl('/job/' . $job_id);
}
}
public function viewJobAction()
{
$sm = $this->getServiceLocator();
$SessionCustomer = $this->getSessionCustomer();
if(empty($SessionCustomer->offsetGet('customer_id'))) {
return $this->redirect()->toRoute('login');
} else {
$JobTable = $sm->get('Job\Model\JobTable');
$JobItemTable = $sm->get('Job\Model\JobItemTable');
$jobId = $this->params()->fromRoute('id');
$Job = $JobTable->getJobById($jobId);
$JobItems = $JobItemTable->getJobItemsByJobId($jobId);
$this->layout()->setVariable('title', 'Order #' . $jobId);
$viewModel = new ViewModel();
$viewModel->setVariables(array(
'Job' => $Job,
'JobItems' => $JobItems
));
return $viewModel;
}
}
private function getSessionCustomer()
{
if(!$this->SessionCustomer) {
$this->SessionCustomer = $this->getServiceLocator()->get('Session\Customer');
}
return $this->SessionCustomer;
}
}
I don't think its an overkill, but I usually avoid calling getServiceLocator() in Controllers.
What you are asking about is basically making sure that the controller's dependency requirement is met. You can use a Factory for the same purpose and do this more sophisticated way. You can create a factory and inject the dependencies directly into the controller. This you will never make a call to the non-object variables.
For that you will be required to create a class that implements a FactoryInterface which will have a method createService which will provide you with ServiceLocator. You can use that serviceLocator to get all the dependencies and inject them directly into your Class.
I'm currently working on my first Silex (2.0) project. I have some Pheasant models defined, which I can access from within my controller:
// it is used both static
$p = \Model\Post::oneById(3);
// .. and with an instance
$p = new \Model\Post;
$p->title = 'foobar';
$p->save();
Now, in some cases I'd like to access the Application class within my model. For example, to check if we're running in debug mode (or not). Right now:
public function beforeSave() {
global $app;
if($app['debug']) {
// ...
}
}
But that doesn't feel like Silex. So I figured I need some kind of thing that'll automatically inject the $app class to my models:
class PheasantModelReflector {
protected $app;
public function __construct(\Silex\Application $app) {
$this->app = $app;
}
public function __get($className) {
$r = (new ReflectionClass(sprintf('Model\%s', $className)))->newInstance();
$r->__invoke($this->app);
return $r;
}
}
$app['model'] = function ($app) {
return new PheasantModelReflector($app);
};
This works to a certain level, but always returns a new instance of the Post model when called like $app['model']->Post.
Is there any way to fix this?
Without having worked with Pheasant, I may try to create a service factory that injects the $app (or the $debug var) into your entity class like so (one problem here is that your entity must extend \Pheasant\DomainObject and you can't override the constructor as it's marked as final):
<?php
// in the example below I inject the whole $app, if you only need the
// debug var, inject only that, injecting the whole $app may
// tempt you to use it as a service locator
// creating a new instance
$app['model_post_factory'] = $app->factory(function ($app) {
$post = new \Model\Post();
$post->setApp($app);
return $post;
});
$post = $app['model_post_factory'];
// getting an instance by ID, here it gets a little tricky
$app['model_post_static_factory'] = function($app, $id) {
$post = \Model\Post::oneById($id);
$post->setApp($app);
return $post;
}
$postById = $app->raw('model_post_static_factory');
$post = $postById($app, $id); // $id comes from somewhere else
// depending on the PHP version you may try directly:
// $post = $app->raw('model_post_static_factory')($app, $id);
The problem with the static method is passing the id parameter. You can import into the factory scope the $id from the outside scope using the use keyword, but IMHO this is too magic (though I don't quite like the alternative either). There may be some more elegant way, but I can't think of right now.
To avoid to generate a new instance of model everytime you need to create a shared service (http://silex.sensiolabs.org/doc/services.html).
For example:
$app['model'] = $app->share(function () {
return new PheasantModelReflector();
});
Anyway inject $app is not necessarily a good idea, maybe you can just inject the dependency every object has? Something like:
new \Model\Post($app['debug']);
OK. here is what I'm trying to do:
class Image{
public $_image;
public $_extension;
public $_mime;
public $_size;
public $_location;
public $_description;
public function __construct($image, $location){
$this->_image = $image;
$this->_location = $location;
$this->_extension = getExtension();
$this->_mime = getMime();
$this->_size = getSize();
}
private functions fallow.....
}
But I keep getting an internal server error when I try to run it. When I comment out the method calls it works. So the question is can I call methods from inside the constructor or am I doing something wrong with the methods.
Do your functions getExtension, getMime and getSize exist? Are they methods on this class? If they are methods, they need to be called with $this->... as in
$this->_extension = $this->getExtension();
If they are not methods, and are functions, you need to make sure the files that contain/define them are loaded before you run the constructor.
Well ..this fragment of code will work as expected:
class Foo
{
protected $secret = null;
public function __construct( $data )
{
$this->secret = $this->makeSecret($data);
}
public function makeSecret( $data )
{
return md5( $data );
}
}
$bar = new Foo( 'lorem ipsum' );
That is not a problem.
But you should know, that is considered to be a bad practice - to do computation/work in the constructor. It makes that class practically untestable. Instead, if you need to perform some computation before "releasing" the object to the rest of the code, you should use a factory. Something along the lines of :
class ImageFactory
{
public function build($image, $location)
{
$instance = new Image($image, $location);
$instance->prepare();
return $instance;
}
}
The class would need some changes:
class Image
{
protected $_image; // you were leaking abstraction
protected $_extension;
protected $_mime;
protected $_size;
protected $_location;
protected $_description;
public function __construct($image, $location)
{
$this->_image = $image;
$this->_location = $location;
}
public function prepare()
{
$this->_extension = $this->getExtension();
$this->_mime = $this->getMime();
$this->_size = $this->getSize();
}
private functions fallow.....
}
Now when you need to create new object you do:
$factory = new ImageFactory;
$image = $factory->build( $file, '/uploads/' );
Of course the instance of ImageFactory can be reusable, and if all your images use the same $location, then you would pass that variable to factory at the initialization. And the factory would be able to "remember it" and pass to all the images it creates:
$factory = new ImageFactory('/uploads/');
$img1 = $factory->build( $file );
$img2 = $factory->build( $something_else );
This is actually how one should deal with creating multiple objects, which all need access to same DB connection instance.
Yes, you can call methods from within the constructor. Remember that the __construct() magic method was implemented in PHP 5. Prior to that, you created a function named the same as your class which acted as your constructor so depending on your PHP version, that could be a problem.
Additionally, the function calls you are making, are they in the class or external? If they are inside the class you need to call them this way:
$this->_extension = $this->getExtension();
You didnt specified what error you are expiriencing clearly. But try calling you class methods even inside the class using this keyword, otherwise it would not work:
public function __construct($image, $location)
{
$this->_image = $image;
$this->_location = $location;
$this->_extension = $this->getExtension();
$this->_mime = $this->getMime();
$this->_size = $this->getSize();
}
Would be a better idea to post your code for the methods you wrote. There could be something wrong within them as well. Possibly forgetting to return a result or something...