I have a problem like below:
I have an Interface name IBannerService
<?php
namespace App\Interfaces;
interface IBannerService
{
public function add($data);
public function list();
public function get($data);
public function delete($data);
}
and an instance name BannerService
class BannerService implements IBannerService
{
public function add($data)
{
return true;
}
public function list()
{
return true;
}
public function get($data)
{
return true;
}
public function delete($data)
{
return true;
}
public function test()
{
print_r("aaaa");
die();
}
}
finally I have a Controller name HomeController
class HomeController extends Controller
{
public function __construct(
IBannerService $bannerService
)
{
$this->bannerService = $bannerService;
}
public function index()
{
$listBanner = $this->bannerService->list();
$this->bannerService->test();
}
}
My configuration:
class DIServiceProvider extends ServiceProvider
{
$this->app->bind(
'App\Interfaces\IBannerService',
'App\Services\BannerService'
);
}
In app.php:
'providers'=>[
App\Providers\DIServiceProvider::class,
]
The code run well with $listBanner = true (just for testing).
The problem is:
Test Method was not declared in interface IBannerService but still go through and print out "aaa" the die.
Did I do something wrong?
Please suggest me, thank you!
That's perfectly normal functionality.
In the Laravel container you defined that when you ask for a IBannerService object, you want to get a BannerService class. And that is what you got. BannerService is an implementation of IBannerService, so no problem for the typehint.
A class is not limited to the functions defined by its interface so you can add as many other functions as you like. I wouldn't recommend it though, things like smart IDE's and phpstan would give you errors or warnings because to them, the variable is an implementation of IBannerService and this does not have a test() function.
If you really want to use more functions I would even recommend to use BannerService as the typehint. This way, static code analysis will still work.
Related
Assume we have the following class (simplified):
class SuperConfig {
public $mainDir;
public function setDir() {
$this->mainDir = "path/to/dir";
}
}
This class is supposed to be extended in EVERY other class in the project, and I do need the setDir() function of the parent to be executed. Obviously, I could do it like this:
class A extends SuperConfig() {
public function __construct() {
parent::setDir();
}
// ... other stuff is about to be done ...
}
and I could access the properties in the child class like this:
class A extends SuperConfig {
public function doSomething() {
SuperConfig::mainDir;
}
}
This is a viable solution, but I got multiple hundreds of classes and doing this in every single one seems tedious. So, is there a way to do something like this:
class SuperConfig {
public $mainDir;
public function __extend() {
$this->setDir();
}
public function setDir() {
$this->mainDir = "path/to/dir";
}
}
__extend() obviously doesn't work like that, but I'm wondering is there is a trick how I could make this work.
class SuperConfig {
public $mainDir;
public function __construct() {
$this->setDir(); // consider moving setDir's code here as well,
// unless you have a good reason for it to be a method
}
public function setDir() {
$this->mainDir = "path/to/dir";
}
}
You simply do this, and then you expect all subclasses to call the parent constructor if they're overriding the constructor:
public function __construct() {
parent::__construct();
// more code
}
It's perfectly reasonable to expect children to call their parent constructor, unless they deliberately want to leave the instance in an unknown and potentially broken state.
Put the constructor in the class that is being extended.
class SuperConfig {
public $mainDir;
public function __construct() {
$this->setDir();
}
public function setDir() {
$this->mainDir = "path/to/dir";
}
}
In any class that extends SuperConfig, if they have a constructor also, be sure to include parent::__construct(); so that setDir is called.
Read deceze's answer for an actual solution to your problem.
I would like to point out tho, that you should not extend every class in your project from a Config class. There are several ways how you could improve that.
1.) Create a static config class which you simply can call everywhere without the need of creation
class SuperConfig {
protected static $mainDir = null;
public static function setMainDir($dir) {
self::$mainDir = $dir;
}
}
2.) Create a trait rather then a parenting class.
trait SuperConfig {
protected $mainDir = null;
public function setMainDir($dir) {
$this->mainDir = $dir;
}
}
which you then can use inside your classes:
class XYZ {
use SuperConfig;
public function doSomething() {
$this->setMainDir('path/to/your/dir/');
}
}
Note that you can do that in the constructor too (which is kinda what you want).
Im not saying those two solutions are the best, but I dont like the thought of extending all classes from a config class. Just does not make much sence. Just imagine that you can only extend from one class per time, while you can use as many traits as you wish (and also have as many static classes as you need).
Well, in this particular case you just need:
class SuperConfig {
public $mainDir = "path/to/dir";
}
Im trying to pass objects between classes in code igniter and am currently failing. What am I doing wrong. Let me strt showing the pure.php version
Errors.php
<?php
class Errors
{
public function __construct(){}
public function setError($msg){}
}
OtherClass.php
<?php
class OtherClass
{
public function __construct(Errors $errorObject) {}
public function someMethod() {}
}
Then in my main controller..
Controller.php
<?php
class Main
{
public function __construct()
{
$this->errors = new Errors;
$this->other = new OtherClass($this->errors);
}
}
By doing this. I can add errors as I go to my error Object, across any objects i instantiate from the Main controller.
Now my code igniter version looks like this
/library/Errors.php
<?php
class Errors
{
public function __construct(){}
public function setError($msg){}
}
/library/OtherClass.php
<?php
class OtherClass
{
public function __construct(Errors $errorObject) {}
public function someMethod() {}
}
Then in my main controller..
Controller.php
<?php
class Main extends CI_Controller
{
public function __construct()
{
$this->load->library('Errors');
$this->load->library('OtherClass',$this->errors);
}
}
When I do this I get an error in my OtherClass saying that $errorObject is not an instance of Errors. Why is the object not being passed?
The problem is with $this->load->library which is defined like this.
public function library($library, $params = NULL, $object_name = NULL)
$params is expected to be an array. If it is not then the $params is set to NULL.
To get around this requires a bunch of monkey biz.
class Errors is unchanged but class OtherClass needs to be changed to...
class OtherClass
{
public function __construct($errorObject)
{
var_dump($errorObject[0]); //so we can prove it got passed
}
public function someMethod(){}
}
Note the removal of the type hint Error from the constructor declaration. Also, we access index 0 of the argument. The reason lies in what happens at the controller.
function __construct()
{
parent::__construct();
$this->load->library('Errors');
$this->load->library('OtherClass', [$this->errors]);
}
We have to put $this->error in an array so that load->library() won't mess with it.
The alternative is to not use "The CodeIgniter Way" and use good old fashion `new' instead. The controller then is...
function __construct()
{
parent::__construct();
$this->load->library('Errors');
$this->other = new OtherClass($this->errors);
}
And other class reverts to...
class OtherClass
{
public function __construct(Errors $errorObject)
{
var_dump($errorObject);
}
public function someMethod(){}
}
Now the problem is that without adding an autoloader to the system you wind up with
Fatal error: Class 'OtherClass' not found in ...
This LINK goes to a page that talks about the various ways to add an autoloader to CI. I know this has been answered on SO too. But I'm failing to find it at the moment.
I am not sure how to name this, but here it goes. Lets suppose i have the following
class A {
public function aa() {
$this->bb();
}
public function bb() {
}
}
class B extends a {
}
class C {
__construct(B $service) {
$this->service = $service;
}
public function aa() {
$this->service->aa();
}
}
My call in code will be
$C = new C(new B());
$C->aa();
So this will basically execute A:aa() which is what i want. As you can see, in A::aa() AA::bb() is called.
What I need. When AA::bb() is called i want to execute some code defined in class C, but I am not allowed to change the A class. I can only change the B class or the C class.
My idea was to add a listener in the B class and overwrite the bb() function like this
class B extends a {
public $listener;
bb() {
parent::bb();
$this->listener();
}
}
class C {
__construct(B $service) {
$this->service = $service;
}
public function aa() {
$this->service->listener = function() { }
$this->service->aa();
}
}
But I don't like this idea a lot, doesn't look like a good one. What are my options here?
Again, I CANNOT change the A class and i can only call the C class.
PHP version is 5.3
You have two options. Extend or decorate.
First one would be kinda what you have already written, though, I would not use public visibility for the listener:
class Foo extends A {
private $listener;
public function setListener(callable $func) {
$this->listener = $func;
}
public function bb() {
call_user_func($this->listener);
return parent:bb();
}
}
In the example I passed the listener via setter injection, but you can also use constructor injection and pass the $listened in the overloaded __construct() method. When you extend a class, the "interface restriction" does not aply to the constructor's signature.
The other approach is to use a decorator:
class Foo {
private $target;
public function __construct(A $target) {
$this->target = $target;
}
public function bb($callback) {
$callback();
return $this->target->bb();
}
public function __call($method, $arguments) {
return call_user_func_array(
array( $this->target, $method ),
$arguments
);
}
}
The second approach would let you alter the interface.
Which option you pick depend on the exact functionality you actually need to implement. The decorator is a solution for, when you need drastic change in the objects behavior - for example, it is really good for adding access control.
I understand that you want to execute code in C after code in A completes. You cannot change A.
As written, C::aa calls A::aa, which calls A::bb and the stack unwinds. Why not just do the work in C::aa after the service call finishes?
class C {
public function aa() {
$this->service->aa();
// whatever you want to do
}
}
If, on the other hand, you need to call code after A::aa is called but before A::bb is called then the example you posted would suffice with clarity:
class B extends a {
public $listener;
public function bb() {
call_user_func($this->listener);
parent::bb();
}
}
Note the use of call_user_func, which is necessary for PHP 5.3 to call an anonymous function stored in a member variable.
trait TestTrait
{
public function matches()
{
}
}
class TestClassOne
{
public static function matches()
{
}
}
class TestClassTwo extends TestClassOne
{
use TestTrait {
matches as alternativeMatches;
}
public function run()
{
$this->alternativeMatches();
}
}
When calling the method run as seen above it will return the error :Fatal error: Cannot make static method matches non static
The classes are not written by me and the trait I have used in multiple other locations. A hell if I have to rewrite the trait .. or have to make a new one , just to make it work with this class.
Is there a way to make it work ?
Cheers
Well I made it work with a bit of a hack ... but I do not like it...
Someone has a nicer way of doing this ?
class TestClassTwo extends TestClassOne
{
use TestTrait {
matches as alternativeMatches;
}
public function run()
{
$this->alternativeMatches();
}
public static function matches()
{
parent::matches();
}
}
I have an abstract class that extends classes to provide a basic orm function. All the functions it provides are protected to the class so it can decide what fields are made publicly available to outside objects. But recently, I have started working with some smaller data classes that do not require such complexity, and would benefit from having the orm editing functions publicly available and no special functions.
As the naming convention for the functions is sufficient and compact, is there a way to change the existing functions to public (without needing the same class, or an interim extends), or would I have to use the new traits feature of php to add an existing class, which contains public versions of the functions that act as an abstraction layer for the internal protected functions?
EDIT:
For the traits method, I was thinking that it would help like this:
abstract class ORMClass {
public function __construct($pk) {}
protected function __get($k) {}
protected function __set($k,$v) {}
protected function save() {}
}
trait publicORM {
public function __get($k) { return parent::__get($k); }
public function __set($k,$v) { return parent::__set($k,$v); }
public function save() { return parent::save(); }
}
class myOrm extends ORMClass {
use publicORM;
protected static $table = 'myTable';
}
so then I could use myOrm like:
$myOrm = new myOrm(1);
$myOrm->foo = 'alice'
echo $myOrm->bar;
$myOrm->save();
without needing the:
public function __get($k) { return parent::__get($k); }
public function __set($k,$v) { return parent::__set($k,$v); }
public function save() { return parent::save(); }
to be listed in the class myOrm
Since this was never answered properly, I'm adding Charles answer.
This can be done using PHP's Reflection library, built in to PHP since version 5. This particular method is fairly hacky:
<?php
abstract class BaseClass {
protected function testMe() {
echo 'I WORK!';
}
}
class ConcreteClass extends BaseClass {
// Class Code
}
$method = new ReflectionMethod('BaseClass', 'testMe');
$method->setAccessible(true);
$method->invoke(new ConcreteClass()); // Prints 'I WORK!'
And here is the better method using an interim abstract class that extends the base class but uses public methods:
<?php
abstract class BaseClass {
protected function testMe() {
echo 'I WORK!';
}
}
abstract class PublicBaseClass extends BaseClass {
public function testMe() {
parent::testMe();
}
}
class ConcreteClass extends PublicBaseClass {
// Class Code
}
$obj = new ConcreteClass();
$obj->testMe();