I have this class:
abstract class Hotel
{
protected $util;
function __construct()
{
$this->util = new Utility();
}
function add(Validate $data, Model_Hotel $hotel){}
function delete(){}
function upload_image(array $imagedetails, $hotel_id){}
}
and a class that extends it
class Premium extends Hotel
{
function add(Model_Hotel $hotel)
{
$hotel->values;
$hotel->save();
}
function upload_image(array $imagedetails, $hotel_id)
{
$this->util->save_image($imagedetails, $hotel_id);
}
}
but then I get an error:
"declaration of Premium::add must be compatible with Hotel::add"
as you can see, I left out a parameter for the add() method, which was intentional
what OOP facility would allow me to inherit a function whose parameters I can change? (Obviously an abstract class won't do here)
It's an E_STRICT error. In PHP you can't overload methods (that's the OOP paradigm you're looking for), so your signature must be identical to the abstract version of the method, or else it's an error.
You could make your $data parameter optional
class Hotel {
function add( Model_Hotel $hotel, Validate $data = null );
}
Related
Is it possible to override an abstract function in PHP 7 with a function in a child class that would narrow down on the accepted argument type?
A word of elaboration - let's assume that I have an abstract method:
abstract public function setTarget(
AbstractDeliveryTarget $deliveryTarget
): Delivery;
Now in one child class, I'd like to override this method's signature in order for it to accept only a specific parameter type (let's assume that EmailDeliveryTarget extends AbstractDeliveryTarget):
public function setTarget(
EmailDeliveryTarget $emailDeliveryTarget
): Delivery;
PHP interpreter would then complain that the signature of the abstract method cannot be altered in the child class. Is there, then, a way to achieve that type-safety in a child class other than some in-method-body type guards?
This is not a technical limitation; what you're asking for doesn't make sense with the principles of OOP.
Your abstract class is a contract; let's define the base class:
class AbstractDeliverer {
abstract public function setTarget(
AbstractDeliveryTarget $deliveryTarget
): Delivery;
}
And some delivery targets
class AbstractDeliveryTarget {}
class EmailDeliveryTarget extends AbstractDeliveryTarget {}
class SMSDeliveryTarget extends AbstractDeliveryTarget {}
Then you can write this somewhere else:
function deliverAll(AbstractDeliverer $d) {
$e = new EmailDeliveryTarget;
$d->setTarget($e);
$s = new SMSDeliveryTarget;
$d->setTarget($s);
}
Because we know that $d is an AbstractDeliverer, we know that passing $e and $s to it should both work. That is the contract guaranteed to us when we made sure our input was of that type.
Now lets see what happens if you extend it the way you wanted:
class EmailOnlyDeliverer extends AbstractDeliverer {
public function setTarget(
EmailDeliveryTarget $emailDeliveryTarget
): Delivery {
/* ... */
}
}
$emailonly = new EmailOnlyDeliverer;
We know that $e instanceOf AbstractDeliverer will be true, because we've inherited, so we know we can safely call our deliverAll method:
deliverAll($emailonly);
The first part of the function is fine, and will effectively run this:
$e = new EmailDeliveryTarget;
$emailonly->setTarget($e);
But then we hit this part:
$s = new SMSDeliveryTarget;
$emailonly->setTarget($s);
Oops! Fatal error! But the contract on AbstractDeliverer told us this was the correct value to pass! What went wrong?
The rule is that a sub-class must accept all inputs that the parent class would accept, but it can accept additional inputs if it wants, something known as "contravariance". (Return types are instead "covariant": the sub-class must never return a value which couldn't be returned from the parent class, but can make a stronger promise that it will only return a subset of those values; I'll leave you to come up with an example of that one yourself).
Looks like no pretty solutions so we can try use interfaces.
<?php
interface DeliveryTarget {}
class AbstractDeliveryTarget implements DeliveryTarget {
}
class EmailDeliveryTarget extends AbstractDeliveryTarget{
}
abstract class Q {
abstract protected function action(DeliveryTarget $class);
}
class Y extends Q {
protected function action(DeliveryTarget $class){}
}
OR
interface DeliveryTargetAction {}
class AbstractDeliveryTarget {
}
class EmailDeliveryTarget extends AbstractDeliveryTarget implements DeliveryTargetAction {
}
abstract class Q {
abstract protected function action(DeliveryTargetAction $class);
}
class Y extends Q {
protected function action(DeliveryTargetAction $class){}
}
Depends on what you want to do.
I have a state object that extends a base abstract class that implements SplSubject.
This state is then passed to the observers notify method however - my unit tests and IDE complain over type involved.
abstract class TQ_Type implements \SplObserver
{
public function update(TQ_State $tq_state)
{
...
}
}
abstract class TQ_State implements \SplSubject
{
public function __construct()
{
$this->observers = new SplObjectStorage;
}
public function attach(TQ_Type $observer)
{
$this->observers->attach($observer);
}
public function detach(TQ_Type $observer)
{
$this->observers->detach($observer);
}
public function notify()
{
foreach ($this->observers as $observer)
{
$observer->update($this);
}
}
}
The following test yields: PHP Fatal error: Declaration of TQ_State::attach() must be compatible with SplSubject::attach(SplObserver $SplObserver)
class TQTypeStandardAppealTest extends PHGUnit_Internal
{
private $under_test;
public function setUp()
{
$this->under_test = new TQ_Type_StandardAppeal();
}
public function test_pending_standard_appeal_tq_approval_hits_states_notify_method()
{
$state = Mockery::mock('TQ_State_Pending')->makePartial();
$state->shouldReceive('get_event')
->withNoArgs()
->andReturn('Approve');
$this->under_test->update($state);
}
}
I derive from these and those derived classes are the subject of the unit tests...
Is this an issue where mockery is not honouring the hierarchy of the object it is mocking
NB I have used a partial mock here as the actual mock returns a Mockery\CompositeExpectation but I'll deal with that later.
Derp on my behalf.
Plain and simple these must respect the contracts of the interface so they must have SplObserver and SplSubject. Any further requirement to restrict the ability of on object not of the correct type to trigger the update method of an observer must be handled within that method.
I have an abstract class that declares the methods required to its children. It also has a construstor that its children inherits. How can I make the abstract class affect the children of the classes that extends it. To further clarify my question, here is my case:
The Abstract Class (abstract.php):
<?php
include_once 'database.php';
include_once 'validation.php';
abstract class DataOperations extends DatabaseConnection {
//The error string shared by all children of DataOperations
//This will be the message to be displayed in case validation failure
public $validator;
public $err_valid_string;
/**
* The DataOperations' constructor ensures that all of its children can perform database operation
* by automatically starting it for them. In case a child overrides this constructor, this child
* must explicitly start the connection to prevent fatal errors. Also, $validator must be re-instantiated
*/
public function __construct() {
$this->startDBConnection();
$this->validator = new InputValidator();
}
public function __destruct() {
}
abstract public function validateData();
abstract public function loadRecord($key, $cascade);
abstract public function saveRecord();
abstract public function updateRecord();
abstract public function deleteRecord();
}
?>
Now, here is the child object that extends the DataOperations abstract class
class Guest extends DataOperations {
//some properties here
public function validateData() {
//implementation
}
public function newRecord(implementation) {
//implementation
}
public function loadRecord($key, $cascade){
//implementation
}
public function saveRecord() {
//implementation
}
public function updateRecord() {
//implementation
}
public function deleteRecord() {
//implementation
}
}
?>
And here is another class, which is a child of Guest
class Booking extends Guest {
//some properties here
public function validateData() {
//implementation
}
public function newRecord(implementation) {
//implementation
}
public function loadRecord($key, $cascade){
//implementation
}
public function saveRecord() {
//implementation
}
public function updateRecord() {
//implementation
}
public function deleteRecord() {
//implementation
}
}
?>
The problem is, if I remove a method in Booking, say deleteRecord(), PHP won't throw an error because I think abstract class doesn't affect its 'grandchildren'. How can I fix this? I thought of using interfaces but my system already has 11 classes that depends to some methods of the abstract class. It will require intensive refactoring.
As you himself stated interface is best suited solution. Like
include_once 'database.php';
include_once 'validation.php';
interface DbInterface {
abstract public function validateData();
abstract public function loadRecord($key, $cascade);
abstract public function saveRecord();
abstract public function updateRecord();
abstract public function deleteRecord();
}
class DataOperations extends DatabaseConnection {
//The error string shared by all children of DataOperations
//This will be the message to be displayed in case validation failure
public $validator;
public $err_valid_string;
/**
* The DataOperations' constructor ensures that all of its children can perform database operation
* by automatically starting it for them. In case a child overrides this constructor, this child
* must explicitly start the connection to prevent fatal errors. Also, $validator must be re-instantiated
*/
public function __construct() {
$this->startDBConnection();
$this->validator = new InputValidator();
}
public function __destruct() {
}
}
class Guest extends DataOperations implements DbInterface {
- - -
}
class Booking extends Guest implements DbInterface {
- - -
}
First as you see I removed abstract from parent class as I assuming only those methods are abstract. Second as per your problem of 11 classes depend on Abstract class, I would say As you only remove abstract methods, Class implementing abstract methods now should implement interface. It is one time needed task. While classes using other normal methods of abstract class work like previous.
The best and cleanest way would be to have your "BOOKING" class extend the "DATAOPERATIONS" class, instead of GUEST, because looks like you don't have any extra methods in the BOOKING class. other wise make and interface and implement it. That is not the preferred way but you would have to give more info your situation.
To be clear, re-declaring a method in a child class will overwrite the parent class's implementation of that method when called from the child class, while not affecting any additional functionality provided by extending the parent class:
class a
{
function hello()
{
echo "Hello";
}
function goodbye()
{
echo "Goodbye";
}
}
/**
* class b overwrites class a's implementation of method goodbye but will retain
* it's definition for method hello
*/
class b extends a
{
function goodbye()
{
echo "See ya!";
}
}
$object = new b();
$object->hello(); // Hello
$object->goodbye();// See ya!
It appears that you want to implement a consistent interface across multiple class definitions. If this is the case, you will likely want to explore using PHP's interfaces.
These allow you to specify the methods that must exist in your class definition along with their set of arguments (collectively known as the signature). Your class definitions will implement an interface and if your definition does not meet the interface implementation specification, a fatal error will be thrown.
From the PHP manual:
// Declare the interface 'iTemplate'
interface iTemplate
{
public function setVariable($name, $var);
public function getHtml($template);
}
// Implement the interface
// This will work
class Template implements iTemplate
{
private $vars = array();
public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}
public function getHtml($template)
{
foreach($this->vars as $name => $value) {
$template = str_replace('{' . $name . '}', $value, $template);
}
return $template;
}
// This will not work
// Fatal error: Class BadTemplate contains 1 abstract methods
// and must therefore be declared abstract (iTemplate::getHtml)
class BadTemplate implements iTemplate
{
private $vars = array();
public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}
}
You can find more information about interface in the PHP manual:
http://us2.php.net/interface
Finally, it looks like you are hoping to define a common constructor for the child classes. Your child classes can both extend the DataOperations class while implementing a separate interface:
class Guest extends DataOperations implements DatabaseWriter
...
I have a strange warning concerning PHP strict standards in my debug.log.
PHP Strict standards: Declaration of gb_EntryList::fields() should be compatible with mgr_LinearModelCollection::fields($mKey = NULL) in ... on line 339
So far so bad, but line 339 holds the definition of the gb_EntryList class
class gb_EntryList extends mgr_mySQLModel
and gb_EntryList does not define fields() at all. It inherits this from mgr_mySQLModel:
abstract class mgr_mySQLModel extends mgr_LinearModelCollection implements mgr_mySQLModelUpdateable {
...
function fields($mKey = null) { ... }
}
Originally, I forgot to put the = null into the declaration, which produced similar messages about mgr_mySQLModel. But these are gone now.
The code runs nicely, but what does this message want to tell me?
PHP version:
PHP 5.4.4-14+deb7u5 (cli) (built: Oct 3 2013 09:24:58)
Update:
I dug a little deeper into the issue. Interestingly the following code should have the same structure, but is fine with php -d error_reporting=4095 -l:
abstract class A {
function foo($sText = ""){
echo "A.foo($sText)\n";
}
abstract function bar();
}
abstract class B extends A {
function foo($sText = ""){
echo "B.foo($sText)\n";
}
}
class C extends B {
function bar(){
echo "C.bar()\n";
}
}
$c = new C();
$c->foo('in C');
The actual classes are too big to reproduce them here. But the structure is apart from interface inheritence the same. However, the above code appears to be fine. The actual situation is slightly more complex:
abstract class mgr_LinearModelCollection implements IteratorAggregate, Countable, ArrayAccess {
protected $sItemClass;
function getIterator() { }
function count(){ }
function offsetExists($offset){ }
function offsetGet($offset){ }
function offsetUnset($offset){ }
function offsetSet($offset, $value){ }
function fields($mKey = null){
$aDummy = call_user_func(array($this->sItemClass,'dummyArray'),$mKey);
return array_keys($aDummy);
}
}
interface mgr_mySQLModelUpdateable {
static function updateTable(array $aOpts = array());
}
abstract class mgr_mySQLModel extends mgr_LinearModelCollection implements mgr_mySQLModelUpdateable{
protected $aFieldNames = null;
function fields($mKey = null){
if(!is_array($this->aFieldNames)) return false;
return $this->aFieldNames;
}
}
class gb_EntryList extends mgr_mySQLModel {
static function updateTable(array $aOpts = array()){ }
}
There are of course many more functions and the { } are filled with code, but apart from that this is the real unchanged code, which produces the said error message. I currently lack any idea, why the toy model is fine, but the real one is not. Any hints?
This E_STRICT message warns about a violation of the Liskov substitution principle. It means that a method that was already defined in the superclass needs to be callable with the arguments of the method in the superclass.
This will trigger the message:
class Super {
public function foo($argument) {
echo $argument;
}
}
class Child extends Super {
public function foo() {
parent::foo('bar');
}
}
Output:
Strict Standards: Declaration of Child::foo() should be compatible
with Super::foo($argument) in ... on line 15
The fact that you say that field() is not declared in gb_EntryList is the clue to what's wrong. mgr_mySQLModel is an ABSTRACT class, if you look in the PHP manual entry for abstract classes the clue is in the line that says "When inheriting from an abstract class, all methods marked abstract in the parent's class declaration must be defined by the child". So although PHP is magically working out that you want the parent field() method, you really should also be declaring it in the child class too.
in my Symfony project I would use a new strategy for manage the data form.
I don't want use the Symfony Form object, but I want use the model to build them.
I don't want to redeclare the Base Doctrine_Record class, so I wrote a new Doctrine_Template: ExtendedModel.
In the ExtendeModel template I've new objects and methods, but I need to override the validate() function of Doctrine_Record.
I tried with
class ExtendedModel extends Doctrine_Template {
[...]
public $validatorSchema;
public function setValidatorSchema(sfValidatorSchema $validatorSchema) {
$this->validatorSchema = $validatorSchema;
}
public function getValidatorSchema() {
return $this->validatorSchema;
}
public function validate() {
$this->getInvoker()->setup();
$errorStack = $this->getInvoker()->getErrorStack();
if ($this->getValidatorSchema()) {
try {
$this->getValidatorSchema()->addOption('allow_extra_fields', true);
$this->getValidatorSchema()->clean($this->getInvoker()->toArray(false));
} catch (sfValidatorErrorSchema $errorSchema) {
$errorStack = $this->getInvoker()->getErrorStack();
foreach ($errorSchema->getErrors() as $key => $error) {
/* #var $error sfValidatorError */
$errorStack->add($key, $error->getMessage());
}
}
}
$this->getInvoker()->validate();
}
}
but Doctrine use the original validate() function.
I want to override some Doctrine_Record functions with a new methods declared into my Doctrine_Template.
Could you suggest me a solution for this problem?
Tnx!
Templates do not override Doctrine_Record methods, they are only fallbacks invoked via the PHP magic __call method when a native method isn't found.
To do this, you need to have your own class in the Doctrine_Record inheritance chain. Fortunately, this is pretty easy:
1. Create myDoctrineRecord
abstract class myDoctrineRecord extends sfDoctrineRecord
{
public function commonRecordMethod() { }
}
I place this file in lib/record, but you can put it anywhere that the autoloader will see it.
2. Set Symfony to use this class in the configureDoctrine callback of your ProjectConfiguration:
public function configureDoctrine(Doctrine_Manager $manager)
{
sfConfig::set('doctrine_model_builder_options', array('baseClassName' => 'myDoctrineRecord'));
}
This is copied/pasted from my previous answer to a similar question. You'll have to rebuild the model as well.