PHP - Protected/Abstract method to use class declared in child class - php

I have an abstract class, in which I want to call method, from a class that is declared in the child (extending) class. An example looks like this:
The abstract class:
abstract class NumberGenerator
{
protected function generate($input){
return MyClass::MyMethod($input);
}
}
My child/extending class:
use TomsFolder\MyClass;
use MyFolder\NumberGenerator;
class TomsNumberGenerator extends NumberGenerator
{
public function generate(string $applicantId): string
{
return $this->generate();
}
}
Another child/extending class:
use DavesFolder\MyClass;
use MyFolder\NumberGenerator;
class DavesNumberGenerator extends NumberGenerator
{
public function generate(string $applicantId): string
{
return $this->generate();
}
}
So I want to call MyClass::MyMethod in NumberGenerator. However it is only imported in TomsNumberGenerator.
The reason I want to do it like is because, I have classes like DavesNumberGenerator which calls a different MyClass.
When I try this, I get 'MyClass is not found in NumberGenerator'. Is there any way to make this work?

Try putting the namespace use statement before the actual class:
NumberGenerator.php
use MyFolder\MyClass;
abstract class NumberGenerator
{
protected function generate($input){
return MyClass::MyMethod($input);
}
}
EDIT
Try this:
NumberGenerator.php
abstract class NumberGenerator
{
protected function generate($class_name, $input){
return call_user_func($class_name . '::MyMethod', $input);
}
}
TomsNumberGenerator.php
use TomsFolder\MyClass;
use MyFolder\NumberGenerator;
class TomsNumberGenerator extends NumberGenerator
{
public function generate(string $applicantId): string
{
return $this->generate(get_class(new MyClass()), $applicantId);
}
}

You have to use interface for this.
You can do the following
Create MyClassInterface
interface MyClassInterface {
public function MyMethod();
}
Implement this interface in some classes
class MyClass1 implements MyClassInterface {
public function MyMethod() {
// implementation
}
}
class MyClass2 implements MyClassInterface {
public function MyMethod() {
// implementation 2
}
}
Add abstract method to NumberGenerator
abstract class NumberGenerator {
abstract protected function GetMyClass(): MyClassInterface;
protected function generate($input){
return $this->GetMyClass()->MyMethod($input);
}
}
Implement GetMyClass function inside child classes
class TomsNumberGenerator extends NumberGenerator
{
protected function GetMyClass(): MyClassInterface {
return new MyClass1();
}
}
class DavesNumberGenerator extends NumberGenerator
{
protected function GetMyClass(): MyClassInterface {
return new MyClass2();
}
}
PS If you want to use static, you can change abstract inside NumberGenerator class, to change string. In this case, your generate will look like this:
protected function generate($input){
return call_user_func($this->GetMyClass() . '::MyMethod', [$input]);
}

Related

get name of extended class in abstract base class

I have an abstract base class implements the laravel queueable trait:
abstract class BaseJob {
use Queueable;
}
Queueable defines $queue:
trait Queueable
{
public $queue;
}
In the actual job it is now possible to defined the classname as the queue name:
class SpecificJob extends BaseJob {
public $queue = self::class;
public function __construct($someParameter) {
// they all have custom constructors so they would overwrite the BaseJob constructor and I would also like to avoid calling parent::__construct;
}
}
Is there some way to do this in the baseclass so it is not required to do it in every class that extends from it?
I thought of something like this:
abstract class BaseJob {
use Queueable;
public $queue = static::class;
}
However this is not possible:
'static::' is not allowed in compile-time constants
You can use late static bindings in the Queueable trait to get the implementing class name if you use a getter rather than a public property.
trait Queueable
{
public function getQueue()
{
return static::class;
}
}
abstract class BaseJob {
use Queueable;
}
class SpecificJob extends BaseJob {
public function __construct($someParameter) {
// they all have custom constructors so they would overwrite the BaseJob constructor and I would also like to avoid calling parent::__construct;
}
}
$mySpecificJob = new SpecificJob('wombats');
assert($mySpecificJob->getQueue() == 'SpecificJob', 'We expect the trait to return the class name of the SpecificJob instance');
echo $mySpecificJob->getQueue().PHP_EOL;
The convention of relying on the implementing class name for the queue name give me hives, personally, I would want to be explicit when naming the queues. From your example, I don't see a reason to use a trait, you can do everything you need to do in the base class if you require sub classes to implement a method that returns the queue name. You could even use the class name by returning the value of static::class if you want.
abstract class BaseJob
{
public abstract function getQueue(): string;
}
class SpecificJob extends BaseJob
{
public function __construct($someParameter)
{
// they all have custom constructors so they would overwrite the BaseJob constructor and I would also like to avoid calling parent::__construct;
}
public function getQueue(): string
{
return self::class;
}
}
class CustomNameJob extends BaseJob
{
private const _queueName = 'custom_queue';
public function __construct($someParameter)
{
// they all have custom constructors so they would overwrite the BaseJob constructor and I would also like to avoid calling parent::__construct;
}
public function getQueue(): string
{
return self::_queueName;
}
}
$mySpecificJob = new SpecificJob('wombats');
assert($mySpecificJob->getQueue() == 'SpecificJob', 'We expect the the class name of the SpecificJob instance');
echo $mySpecificJob->getQueue().PHP_EOL;
$myCustomNameJob = new CustomNameJob('wombats');
assert($myCustomNameJob->getQueue() == 'custom_queue', 'We expect the queue name specified in the CustomNameJob class');
echo $myCustomNameJob->getQueue().PHP_EOL;

PHP Class inheritance

Suppose I have the following :
<?php
class Final extends Intermediate {
public function final_level() {
$this->low_level();
$this->inter_level();
}
}
class Intermediate extends Lib1 {
public function inter_level() {
$this->low_level();
}
}
class Lib1 {
public function low_level1();
public function low_level2();
}
class Lib2 {
public function low_level1();
public function low_level2();
}
I would like to change the Intermediate class to extend Lib1 or Lib2, depending on some conditions, without duplicating Intermediate and Final code content.
All low_level functions are the same for both Lib.
In the end, I would like to have a Final1 class that use Lib1 (and Final2 that use Lib2).
How could I achieve this ?
You cannot achieve this via inheritance but you can via delegation
With this approach you delegate the implementation of some methods to a 'delegate' object rather than a base class.
Here it is an example:
<?php
class Final extends Intermediate {
public function __construct(Lib delegate) {
parent::__construct(delegate);
}
public function final_level() {
$this->low_level();
$this->inter_level();
}
}
class Intermediate implements Lib { //here you implement an interface rather than extending a class
private Lib delegate;
public function __construct(Lib delegate) {
$this->delegate = delegate;
}
public function inter_level() {
$this->low_level();
}
public function low_level() {
//delegate!
$this->delegate->low_level();
}
}
class Lib1 implements Lib{
public function low_level(); //implementation #1
}
class Lib2 implements Lib {
public function low_level(); //implementation #2
}
interface Lib {
public function low_level();
}
now you can create your final1 and final2 object in this way:
$final1 = new Final(new Lib1());
$final2 = new Final(new Lib2());
or, if you prefer, you can create the Final1 and Final2 classes extending from Final:
class Final1 extends Final {
public function __construct()
{
parent::__construct(new Lib1());
}
}
class Final2 extends Final {
public function __construct()
{
parent::__construct(new Lib2());
}
}
$final1 = new Final1();
$final2 = new Final2();

Extends a PHP class and it's children

In one of my projects, I use an external library providing two classes : DrawingImage and DrawingCharset, both of them extending BaseDrawing.
I want to extends BaseDrawing to add some properties and alter an existsing method. But I also want theses modifications in "copy" of existing children (DrawingImage and DrawingCharset).
There is a simple way to do it ? Extending don't seems to be a solution : I must duplicate code between each subclass. And I'm not sure i can call a parent method through Trait.
Traits can access properties and methods of superclasses just like the subclasses that import them, so you can definitely add new functionality across children of BaseDrawing with traits.
<?php
class BaseDrawing
{
public $baseProp;
public function __construct($baseProp)
{
$this->baseProp = $baseProp;
}
public function doSomething()
{
echo 'BaseDrawing: '.$this->baseProp.PHP_EOL;
}
}
class DrawingImage extends BaseDrawing
{
public $drawingProp;
public function __construct($baseProp, $drawingProp)
{
parent::__construct($baseProp);
$this->drawingProp = $drawingProp;
}
public function doSomething()
{
echo 'DrawingImage: '.$this->baseProp.' - '.$this->drawingProp.PHP_EOL;
}
}
class DrawingCharset extends BaseDrawing
{
public $charsetProp;
public function __construct($baseProp, $charsetProp)
{
parent::__construct($baseProp);
$this->charsetProp = $charsetProp;
}
public function doSomething()
{
echo 'DrawingCharset: '.$this->baseProp.' - '.$this->charsetProp.PHP_EOL;
}
}
/**
* Trait BaseDrawingEnhancements
* Adds new functionality to BaseDrawing classes
*/
trait BaseDrawingEnhancements
{
public $traitProp;
public function setTraitProp($traitProp)
{
$this->traitProp = $traitProp;
}
public function doNewThing()
{
echo 'BaseDrawingEnhancements: '.$this->baseProp.' - '.$this->traitProp.PHP_EOL;
}
}
class MyDrawingImageImpl extends DrawingImage
{
// Add the trait to our subclass
use BaseDrawingEnhancements;
}
class MyDrawingCharsetImpl extends DrawingCharset
{
// Add the trait to our subclass
use BaseDrawingEnhancements;
}
$myDrawingImageImpl = new MyDrawingImageImpl('Foo', 'Bar');
$myDrawingImageImpl->setTraitProp('Wombats');
$myDrawingCharsetImpl = new MyDrawingCharsetImpl('Bob', 'Alice');
$myDrawingCharsetImpl->setTraitProp('Koalas');
$myDrawingImageImpl->doSomething();
$myDrawingCharsetImpl->doSomething();
$myDrawingImageImpl->doNewThing();
$myDrawingCharsetImpl->doNewThing();

Changing the Privacy Scope of PHP Class Functions

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();

I can't use a static method, but that seems to be reasonable

base class:
abstract class Challenge
{
abstract **static** public function getName();
}
now two classes from it:
class ChallengeType1 extends Challenge
{
public **static** function getName()
{
return 'Swimming';
}
}
class ChallengeType2 extends Challenge
{
public **static** function getName()
{
return 'Climbing';
}
}
as you all might know, we can't use static, but it would be reasonable. So I can't do like that: for example, I have the classname, so I want to know it's name: ChallengeType2::getName(); - it will fail! First I should construct the object - looks unnecessary (not to mention, what it this class have very complex initialization?)
Turns out you cannot have static abstract methods on an abstract class. See here:
Why does PHP 5.2+ disallow abstract static class methods?
But you can declare an interface to require a static method.
Here is an example that compiles:
Working Example
<?php
interface IChallenge
{
static function getName();
}
abstract class Challenge implements IChallenge
{
}
class ChallengeType1 extends Challenge
{
public static function getName()
{
return 'Swimming';
}
}
class ChallengeType2 extends Challenge
{
public static function getName()
{
return 'Climbing';
}
}

Categories