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']);
Related
I am testing a class, let's call it ClassUnderTest using another class, let's call it OtherClass. In my Test I do:
$OtherClassStub = $this->createStub(OtherClass::class);
$OtherClassStub->method(...)
->willReturn(...);
$ClassUnderTest->otherClass = $OtherClassStub;
That works. But when the $ClassUnderTest calls new OtherClass(), the original OtherClass class is created instead of the stub.
How can I achieve that every possible instance of OtherClass in the context of the test is replaced by the stub?
From your description I infer that in principle you have something like this:
class OtherClass {
protected function someMethod(): bool
{
// determine $x ...
return $x;
}
}
class ClassUnderTest {
public OtherClass $otherClass;
public function methodToBeTested(): bool
{
$otherClass = new OtherClass();
return $otherClass->someMethod();
}
}
class ClassUnderTestTest extends TestCase {
public function testMethodToBeTested(): void
{
$otherClassStub = $this->createStub(OtherClass::class);
$otherClassStub->method('someMethod')
->willReturn(true);
$classUnderTest = new ClassUnderTest();
$classUnderTest->otherClass = $otherClassStub;
$result = $classUnderTest->methodToBeTested();
$this->assertTrue($result);
}
}
Now the assertion in your test may hold or it may fail. Why? Because you are not calling the method you stubbed on the $otherClassStub. Instead you instantiate a new $otherClass object in the method you're testing (or somewhere down the line).
Either your ClassUnderTest should always use the OtherClass object from the ClassUndertTest::otherClass attribute (assuming that's why you put it there in the first place).
Or you could use some other form of dependency injection, e.g. by using a framework like Symfony or Laravel. (In the case of Symfony you can even use only the DependencyInjection Component, no idea if that's possible with Laravel, too.)
The simple answer to your actual question is: you cannot change the behaviour of the new keyword. Calling new on a class will always instantiate a new object based on exactly that class, unless the constructor of that class defines something else.
(You might want to get the concept of classes and objects straight, your code example as well as your question seem to indicate that you're not quite clear on that. Maybe reading up on that as well as on the concept of dependency injection will help you.)
Perhaps a solution to your problem is presented here:
How to Build a PHP Plugin Module System
This is one way to load classes as plugins and they can be called from each other. With modifying this system a bit, you can create as many "new OtherClass()" as you like from your code and still access everything from other classes. If you want multiple instances of a class, perhaps modify it into this direction:
function load ($module,$instance) {
if (isset($this->$module->$instance)) { return true; }
From above link:
<?php
class Core {
// (A) PROPERTIES
public $error = ""; // LAST ERROR MESSAGE
public $pdo = null; // DATABASE CONNECTION
public $stmt = null; // SQL STATEMENT
public $lastID = null; // LAST INSERT/UPDATE ID
// (B) LOAD SPECIFIED MODULE
// $module : module to load
function load ($module) {
// (B1) CHECK IF MODULE IS ALREADY LOADED
if (isset($this->$module)) { return true; }
// (B2) EXTEND MODULE ON CORE OBJECT
$file = PATH_LIB . "LIB-$module.php";
if (file_exists($file)) {
require $file;
$this->$module = new $module();
// EVIL POINTER - ALLOW OBJECTS TO ACCESS EACH OTHER
$this->$module->core =& $this;
$this->$module->error =& $this->error;
$this->$module->pdo =& $this->pdo;
$this->$module->stmt =& $this->stmt;
return true;
} else {
$this->error = "$file not found!";
return false;
}
}
}
ps. thank you for the mod, who made me work a bit more to keep this answer online. the answer is so much better now.
I'm working on breaking up a large, monolithic class into several subclasses, but it's too much to do all at once so I'm looking to split them out one by one over several releases as time permits. It's an authentication class that authorizes some channel, so currently it looks like this:
$auth = new Auth($user, $data);
$output = $auth->authChannel($channelName);
Inside Auth, it basically looks like this:
public function __construct($user, $data)
{
$this->user = $user;
$this->data = $data;
}
public function authChannel($channel)
{
$this->setUserData();
if (isset(self::CHANNEL_AUTH_FUNCTIONS[$channel])) {
$authFunction = self::CHANNEL_AUTH_FUNCTIONS[$channel];
return $this->$authFunction();
} else {
// invalid channel
}
}
So self::CHANNEL_AUTH_FUNCTIONS is basically ['channelA' => 'authChannelA', 'channelB' => 'authChannelB'], etc., and all those functions are in this one class.
Now what I want to do, one at a time, is if $legacyChannel => callLegacyFunction() / else $newChannel => instantiate its own class and call auth().
So I put Auth.php into its own namespace and have the new Channel.php class in that same namespace. And Channel extends Auth.
Currently I have this:
public function authChannel($channel)
{
$this->setUserData();
if (isset(self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel])) {
$authFunction = self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel];
if ($authFunction) {
return $this->$authFunction();
} else {
$authClassName = __NAMESPACE__ . '\\' . ucwords($channel);
$authClass = new $authClassName($user, $data);
return $authClass->auth();
}
} else {
// invalid channel
}
}
Is there a better way to do this? Currently it seems a bit wasteful since two different objects are created and the setUserData() function for example would need to be called again I believe. I'm also wondering if there's a better way to get the dynamic class name other than through __NAMESPACE__ . / . $className.
You'll have to work quite a bit until that code starts looking better. I'll try to suggest as few changes as possible, to make "migration" as painless as possible, although you are a few steps removed from a clean design.
To start with, you can create an AuthStrategyInterface for your new authentication classes.
interface AuthStrategyInterface
{
public function supports(string $channel): bool;
public function auth($user, $data);
}
Each of your new authentication classes should implement this interface. The method supports($channel) is easy enough to understand: if a authentication class can deal with certain channel, it should return true.
Your Auth class would need a way to get these strategies injected. Usually you would do that in the constructor... but to leave your API unchanged we'll just create a setter method for that.
When executing authChannel(), it will first check on the injected strategies to see if any supports the used $channel, and use that if possible. If not, goes back to check your old implementations.
This way you do not need to touch any of the old code as you add new authentication strategies. As you add new implementations, you are gradually strangling the legacy system. At one point no of the old implementations are used, and you can move on to a new code refactoring phase.
class Auth {
private iterable $strategies = [];
public function __construct($user, $data)
{
$this->user = $user;
$this->data = $data;
}
public function setAuthStrategies(iterable $strategies)
{
$this->strategies = $strategies;
}
public function authChannel($channel)
{
$this->setUserData();
// check if any of the new strategies supports
foreach ($this->strategies as $strategy) {
if ($strategy->supports($channel) {
return $strategy->auth($this->user, $this->data);
}
}
// check "legacy" authentication methods.
if (isset(self::CHANNEL_AUTH_FUNCTIONS[$channel])) {
$authFunction = self::CHANNEL_AUTH_FUNCTIONS[$channel];
return $this->$authFunction($this->user, $this->data);
}
// no valid authentication method
return false;
}
}
To use it, you would do something like this:
$fooAuthStrategy = new FooAuthStrategy();
$barAuthStrategy = new BarAuthStrategy();
$bazAuthStrategy = new BazAuthStrategy();
$auth = new Auth($user, $data);
$auth->setAuthStrategies(
[
$fooAuthStrategy,
$barAuthStrategy,
bazAuthStrategy
]
);
$auth->authChannel($channel);
The specifics would change according to how exactly your application is set-up, but something like this would take you further in a good direction than your current approach.
I don't know if I understood the question correctly, but you couldn't do it like that?
public function authChannel($channel)
{
$this->setUserData();
if (!isset(self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel])) {
// Invalid channel
return;
}
return self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel]
? $this->$authFunction()
: parent::auth();
}
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.
I am struggling to get dependency injection to work the way I expect -
I am trying to inject a class, Api, which needs to know which server to connect to for a particular user. This means that overriding constructor properties in a config file is useless, as each user may need to connect to a different server.
class MyController {
private $api;
public function __construct(Api $api) {
$this->api = $api;
}
}
class Api {
private $userServerIp;
public function __construct($serverip) {
$this->userServerIp = $serverip;
}
}
How can I inject this class with the correct parameters? Is it possible to override the definition somehow? Is there some way of getting the class by calling the container with parameters?
To (hopefully) clarify - I'm trying to call the container to instantiate an object, while passing to it the parameters that would otherwise be in a definition.
Since IP depends on the user you probably have some piece of logic that does the user=>serverIP mapping. It might be reading from the db or simple id-based sharding, or whatever. With that logic you can build ApiFactory service that creates Api for a particular user:
class ApiFactory {
private function getIp(User $user) {
// simple sharding between 2 servers based on user id
// in a real app this logic is probably more complex - so you will extract it into a separate class
$ips = ['api1.example.com', 'api2.example.com'];
$i = $user->id % 2;
return $ips[$i];
}
public function createForUser(User $user) {
return new Api($this->getIp($user);
}
}
Now instead of injecting Api into your controller you can inject ApiFactory (assuming your controller knows the user for which it needs the Api instance)
class MyController {
private $apiFactory;
public function __construct(ApiFactory $apiFactory) {
$this->apiFactory = $apiFactory;
}
public function someAction() {
$currentUser = ... // somehow get the user - might be provided by your framework, or might be injected as well
$api = $this->apiFactory->createForUser($currentUser);
$api->makeSomeCall();
}
}
I am not sure I understand your question fully, but you can configure your Api class like this:
return [
'Foo' => function () {
return new Api('127.0.0.1');
},
];
Have a look at the documentation for more examples or details: http://php-di.org/doc/php-definitions.html
Edit:
return [
'foo1' => function () {
return new Api('127.0.0.1');
},
'foo2' => function () {
return new Api('127.0.0.2');
},
];
public function getHelperInstance()
{
$user = new Helper();
$user->set($result['data']);
return $user;
}
I am calling getHelper() class multiple times and if $user is not empty than am calling getHelperInstance(), now in my case getHelperInstance() always creates a new instance of Helper() class and so every time I call getHelperInstance() function am creating a new instance of Helper() so is there any way where can I can just create one instance of Helper() and use it multiple times instead of creating a new instance everytime. Any suggestions !!!
public function getHelper()
{
$user = array();
if (!empty($user))
{
$user = $this->getHelperInstance();
}
return $user;
}
Here is what Erich Gamma, one of the Singleton pattern's inventors, has to say about it:
"I'm in favor of dropping Singleton. Its use is almost always a design smell"
So, instead of a Singleton, I suggest to use Dependency Injection.
Create the Helper instance before you create what is $this. Then set the helper instance to the $this instance from the outside, either through a setter method or through the constructor.
As an alternative, create a Helper broker that knows how to instantiate helpers by name and pass that to the $this instance:
class HelperBroker
{
protected $helpers = array();
public function getHelper($name)
{
// check if we have a helper of this name already
if(!array_key_exists($name, $this->helpers)) {
// create helper and store for later subsequent calls
$this->helpers[$name] = new $name;
}
return $this->helpers[$name];
}
}
This way you can lazy load helpers as needed and will never get a second instance, without having to use Singleton. Pass an instance of the broker to every class that needs to use helpers.
Example with a single helper
$helper = new Helper;
$someClass = new Something($helper);
and
class Something
{
protected $helper;
public function __construct($helper)
{
$this->helper = $helper;
}
public function useHelper()
{
$return = $this->helper->doSomethingHelpful();
}
}
Inside $something you can now store and access the helper instance directly. You don't need to instantiate anything. In fact, $something doesn't even have to bother about how a helper is instantiated, because we give $something everything it might need upfront.
Now, if you want to use more than one helper in $someClass, you'd use the same principle:
$helper1 = new Helper;
$helper2 = new OtherHelper;
$something = new Something($helper1, $helper2);
This list will get rather long the more dependencies you insert upfront. We might not want to instantiate all helpers all the time as well. That's where the HelperBroker comes into play. Instead of passing every helper as a ready instance to the $something, we inject an object that knows how to create helpers and also keeps track of them.
$broker = new HelperBroker;
$something = new Something($broker);
and
class Something
{
protected $helperBroker;
public function __construct($broker)
{
$this->helperBroker = $broker;
}
public function doSomethingHelpful()
{
$return = $this->getHelper('foo')->doSomethingHelpful();
}
public function doSomethingElse()
{
$return = $this->getHelper('bar')->doSomethingElse();
}
}
Now $something can get the helpers it needs, when it needs them from the broker. In addition, any class that needs to access helpers does now no longer need to bother about how to create the helper, because this logic is encapsulated inside the broker.
$broker = new HelperBroker;
$something = new Something($broker);
$other = new Other($broker);
The broker also makes sure that you only have one helper instance, because when a helper was instantiated, it is stored inside the broker and returned on subsequent calls. This solves your initial problem, that you don't want to reinstance any helpers. It also doesn't force your helpers to know anything about how to manage themselves in the global state, like the Singleton does. Instead you helpers can concentrate on their responsibility: helping. That's clean, simple and reusable.
It sounds like you are interested in the singleton pattern. If you are using PHP5+, you should be able to take advantage of PHP's OOP stuff.
Here's an article on how to implement a singleton in php4. (But I would strongly suggest updating to php5 if that is an option at all)
class Singleton {
function Singleton() {
// Perform object initialization here.
}
function &getInstance() {
static $instance = null;
if (null === $instance) {
$instance = new Singleton();
}
return $instance;
}
}
PHP 4 Singleton Pattern
FYI, if you have any control over which PHP version you use you really should migrate to PHP 5.