Can a property of an Abstract Class be access outside the class? - php

Is is possible to access a variable in a PHP abstract class, i.e.
abstract class Settings
{
public $application = array
(
'Name' => 'MyApp',
'Version' => '1.0.0',
'Date' => 'June 1, 2017'
)
}
echo Settings::application ['Name']; // doesn't work

You could make the variable static, as long as it doesn't need to allow differentiation across instances (i.e. instance variable):
<?php
// example code
abstract class Settings
{
public static $application = array
(
'Name' => 'MyApp',
'Version' => '1.0.0',
'Date' => 'June 1, 2017'
);
}
echo Settings::$application ['Name'];
Run it in this playground example.
Though your original access of application was similar to that of a constant. use const to declare a Class constant:
abstract class Settings
{
const application = array
(
'Name' => 'MyApp',
'Version' => '1.0.0',
'Date' => 'June 1, 2017'
);
}
echo Settings::application ['Name'];
Run it in this playground example.

Abstract Classes can not be directly instantiated as they rely on
child classes to fully implement the functionality.
So if you want to check your variable I would make new class and inherit from your Settings class. You will have to use it with inheritance anyway.
class MySettings extends Settings
{
....
}
$mySettings = new MySettings();
echo $mySettings->application['Name'];
More about abstract classes http://culttt.com/2014/03/26/abstract-classes/

Related

How can I mock a method which is not defined in the interface?

In my php file I am using Zend\Cache\Storage\StorageInterface. I have bind this interface with Zend\filesystem cache. I have to unit test the main php file. Problem is StorageInterface does not define flush method. Filesystem have flush method from different interface.
In my unit testing I am trying to mock the StorageInterface but problem is it gives an error stating that flush is not defined. How can I stub the flush method?
Binding:
$container->bindIf(
"Zend\\Cache\\Storage\\StorageInterface",
function () {
$directoryPath = "/tmp/zend_report_cache";
if (!file_exists($directoryPath) && !mkdir($directoryPath)) {
throw new Exception("Failed to create directory [directoryPath=$directoryPath]");
}
return \Zend\Cache\StorageFactory::factory(array(
'adapter' => array(
'name' => 'filesystem',
'options' => array(
'cache_dir' => $directoryPath
),
),
'plugins' => array('serializer'),
));
}
);
Main.php (only showing relevant code
use Zend\Cache\Storage\StorageInterface;
public function __destruct()
{
$this->cache->flush();
}
unit test file
private function getCacheMock()
{
$cacheMock = $this->setMethods(['execute'])->createMock(StorageInterface::class);
$cacheMock->expects($this->at(0))->method('setItem')->with(CsvReport::HEADER_CACHE_KEY, []);
$cacheMock->expects($this->at(1))->method('getItem')->with(CsvReport::HEADER_CACHE_KEY)->willReturn([]);
$cacheMock->expects($this->at(2))->method('setItem')->with(CsvReport::HEADER_CACHE_KEY, ['key1', 'key2', 'key3']);
$cacheMock->expects($this->at(3))->method('setItem')->with(0, $this->getMockRow(0));
$cacheMock->expects($this->at(4))->method('getItem')->with(CsvReport::HEADER_CACHE_KEY)->willReturn(['key1', 'key2', 'key3']);
$cacheMock->expects($this->at(5))->method('setItem')->with(CsvReport::HEADER_CACHE_KEY, ['key1', 'key2', 'key3', 'key4']);
$cacheMock->expects($this->at(6))->method('setItem')->with(1, $this->getMockRow(1));
$cacheMock->expects($this->at(7))->method('getItem')->with(CsvReport::HEADER_CACHE_KEY)->willReturn(['key1', 'key2', 'key3', 'key4']);
$cacheMock->expects($this->at(8))->method('setItem')->with(CsvReport::HEADER_CACHE_KEY, ['key1', 'key2', 'key3', 'key4', 'key5']);
$cacheMock->expects($this->at(9))->method('setItem')->with(2, $this->getMockRow(2));
return $cacheMock;
}
Error: Trying to configure method "flush" which cannot be configured because it does not exist, has not been specified, is final, or is static

PHP, OOP: Why can't the array property of my class use properties & methods as values like an array outside of a class can?

So like the title says, I am having a hard time making an array property of one of my classes have it's values be declared as properties & methods.
I can successfully do this if the array is not a property of a class, but as soon as the array is dropped into a class, the script doesn't like those values, and throws me this error.
Fatal error: Constant expression contains invalid operations in C:\xampp\htdocs_webdev\repos\mcf\static\inc\classes\class.catalogue.php on line 17
I have both classes being included in a different .php called inc.classes.php. That file is then included in each page. Here is some code to better illustrate my issue,
Master Class File: inc.classes.php
// config
require_once('config/config.php'); // config file
// other tools
require_once(ROOT_DIR . 'inc/parsedown/Parsedown.php'); // tool that I am using for parsing .md files
// my classes
require_once(ROOT_DIR . 'inc/classes/class.vendor.php');
require_once(ROOT_DIR . 'inc/classes/class.catalogue.php');
Class A: class.vendor.php
class Vendor
{
public $vendor = array(
'foo' => array(
'name' => 'Foo Inc.',
'image' => (VENDOR_IMG . 'foo/foo-logo.png'),
),
'bar' => array(
'name' => 'Bar Co.',
'image' => (VENDOR_IMG . 'bar/bar-logo.png'),
),
);
public function get($data) {
if (array_key_exists($data, $this->vendors)) {
return $this->vendors[$data];
} else {
// throw error
}
}
// Class methods...
}
Class B: class.catalogue.php
class Catalogue
{
public $catalogue = array(
'1' => array(
$section = $markdown->text(file_get_contents(ROOT_DIR . catalogue/markdown/section1.md')),
$link = 'catalogue/pdf/section1.pdf,
$pdf = (ROOT_DIR . $link),
'title' => 'Section One',
'content' => mdReplace($section, $pdf, $link),
'theme' => 'purple',
'vendors' => array(
1 => $vendor->get('foo'),
2 => $vendor->get('bar'),
),
),
// '2' ...
);
// Class methods...
}
(mdReplace() is a small function located in a seperate php file called inc.functions.php. It's purpose is to replace a few keywords inside of the .md files that contain the sections' content.)
Apologies in advance if I am just blind as a bat right now and am missing something obvious.
You can't run methods on a class property like that. You'd need to set that up inside your construct:
class Catalogue
{
public $catalogue = array();
public function __construct()
{
$this->catalogue = array(
'1' => array(
$section = $markdown->text(file_get_contents(ROOT_DIR . catalogue/markdown/section1.md')),
$link = 'catalogue/pdf/section1.pdf,
$pdf = (ROOT_DIR . $link),
'title' => 'Section One',
'content' => mdReplace($section, $pdf, $link),
'theme' => 'purple',
'vendors' => array(
1 => $vendor->get('foo'),
2 => $vendor->get('bar'),
),
),
// '2' ...
);
}
// Class methods...
}
If you read php oop manual carefully, here what you will see:
Class member variables are called "properties"... They are defined by using one of the keywords public, protected, or private, followed by a normal variable declaration. This declaration may include an initialization, but this initialization must be a constant value--that is, it must be able to be evaluated at compile time and must not depend on run-time information in order to be evaluated.
See the words
must not depend on run-time information
And your current definition of public $catalogue is dependant of some data that will be evaluated later. That's why you have fatal error.
So, as said the solution is to fill $catalogue data by calling some function - either explicitly or in a __construct for example.
As Farkie said, you can't run method calls on a class property like the way you did.
The reason is, those objects which you are trying to use are not initialised, and in order for them to work they must be initialised first.
Ex variables which can't directly be used in properly as they are not available to be used
$markdown
$pdf
$section
So for Class B you need to have your code written inside the constructor.
However whatever you have done for Class A is perfectly acceptable and it should work. I could see that you have a typo in the variable name. It should be $vendors as you are trying to refer it inside the function get() as $this->vendors[$data];
The following will work
class Vendor
{
public $vendors = array(
'foo' => array(
'name' => 'Foo Inc.',
'image' => (VENDOR_IMG . 'foo/foo-logo.png'),
),
'bar' => array(
'name' => 'Bar Co.',
'image' => (VENDOR_IMG . 'bar/bar-logo.png'),
),
);
public function get($data) {
if (array_key_exists($data, $this->vendors)) {
return $this->vendors[$data];
} else {
// throw error
}
}
// Class methods...
}

zf2 ServiceManagerAwareInterface in Fieldset

In my Zend\Form\Fieldset AddressFieldset it needs a Zend\Db\TableGateway\AbstractTableGateway BundeslandTable for a \Zend\Form\Element\Select().
So i implement \Zend\ServiceManager\ServiceManagerAwareInterface in this AddressFieldset and use the init() instead __construct().
And in module.config.php (not only in 'form_elements' tested, also in 'service_manager')
'form_elements' => array(
'factories' => array(
'MyFormway\Form\Fieldset\Address' => function($sm) {
$addressFieldset = new MyFormway\Form\Fieldset\AddressFieldset();
$addressFieldset->setServiceManager($sm);
return $addressFieldset;
}
),
),
In a \Zend\Form\Form's init():
$this->add(array(
'type' => 'MyFormway\Form\Fieldset\Address',
'name' => 'address',
));
this throws an error:
Zend\Form\FormElementManager::get was unable to fetch or create an instance for MyFormway\Form\Fieldset\Address
Why is zend unable to fetch an instance of this Fieldset?
edit-----------------------
'form_elements' => array(
'factories' => array(
'MyFormway\Form\Fieldset\Address' => function($formElementManager) {
die('inna form_elements config');
$addressFieldset = new \MyFormway\Form\Fieldset\AddressFieldset();
$addressFieldset->setServiceManager($formElementManager->getServiceLocator());
return $addressFieldset;
}
),
),
Because i have the Zend\Form\FormElementManager i fetch the ServiceLocator ...perhaps dont needed, because all XxxManager extends the Zend\ServiceManager\AbstractPluginManager and this extends ServiceManager.
In FormElementManager and also in AbstractPluginManager are no method getServiceManager().
But my problem: the die() is not called plus the error above. Is it a bug? ...i stand for a big wall :(
edit-----------------------
It works for a Form but not for a Fieldset!!!
Can you do a quick check if the \Invokable is called at all? Some professional die()-debugging will suffice.
Other than that a potential error source would be your injection of the ServiceManager. In the code you provide you're not actually injecting the ServiceLocator but rather the FormElementManager.
$addressFieldset->setServiceManager($sm->getServiceManager());
Doing it this way is considered Bad-Practice tho. You should only inject the stuff that you actually do need. Given you're injecting the whole manager i assume you're either working with Doctrine or you'll need access to some DB-Data. Do it like this:
'Foo' => function ($formElementManager) {
$sl = $formElementManager->getServiceManager();
$fs = new FooFieldset();
$fs->setDbDependency(
$sl->get('MyDbDependency')
);
return $fs;
}
Last little note: when you're adding a Fieldset, you don't need to add 'name' => 'foo' within the $this->add(), since the name of the fieldset will be defined via the Fieldset __construct('name').

Incomplete PHP class when serializing object in sessions

I'm working on a shopping cart (Cart model). One of its protected properties is "_items", which holds an array of Product objects. They (Products) all get stored in DB for populating the session (using ZF, Zend_Session_SaveHandler_DbTable() etc.).
public function addItem(Model_Product $product, $qty)
{
$qty = (int) $qty;
$pId = $product->getId();
if ($qty > 0) {
$this->_items[$pId] = array('product' => $product, 'qty' => $qty);
} else {
// if the quantity is zero (or less), remove item from stack
unset($this->_items[$pId]);
}
// add new info to session
$this->persist();
}
In the controller, I grab a Product obj from DB with the ProductMapper and provide it to "addItem()":
$product1 = $prodMapper->getProductByName('cap');
$this->_cart->addItem($product1, 2);
getProductByName() returns a new populated Model_Product object.
I usually get the
Please ensure that the class definition "Model_Product" of the object you are trying to operate on was loaded _before_ ...
error message, a session dump obviously shows
['__PHP_Incomplete_Class_Name'] => 'Model_Product'
I know about the "declaring the class before serializing it". My problem is this: how can I declare the Product class in addItem(), if it's injected (first param) in the first place? Wouldn't a new declaration (like new Model_Product()) overwrite the param (original object) in addItem()? Must I declare it in the Cart model again?
Besides, I'll surely get a Cannot redeclare class Model_Product if I... redeclare it in Cart.
In ZF's bootstrap, the session was started before autoloading.
/**
* Make XXX_* classes available
*/
protected function _initAutoloaders()
{
$loader = new Zend_Application_Module_Autoloader(array(
'namespace' => 'XXX',
'basePath' => APPLICATION_PATH
));
}
public function _initSession()
{
$config = $this->_config->custom->session;
/**
* For other settings, see the link below:
* http://framework.zend.com/manual/en/zend.session.global_session_management.html
*/
$sessionOptions = array(
'name' => $config->name,
'gc_maxlifetime' => $config->ttl,
'use_only_cookies' => $config->onlyCookies,
// 'strict' => true,
// 'path' => '/',
);
// store session info in DB
$sessDbConfig = array(
'name' => 'xxx_session',
'primary' => 'id',
'modifiedColumn' => 'modified',
'dataColumn' => 'data',
'lifetimeColumn' => 'lifetime'
);
Zend_Session::setOptions($sessionOptions);
Zend_Session::setSaveHandler(new Zend_Session_SaveHandler_DbTable($sessDbConfig));
Zend_Session::start();
}
When I was getting the errors I was talking about, the method declaration was the other way around: _initSession() was first, then _initAutoloaders() - and this was the exact order ZF was processing them.
I'll test some more, but this seems to work (and logical). Thanks for all your suggestions.

Is there a way to override Model properties without defining them all again with Kohana?

I have the following, for example:
class Model_User extends ORM {
protected $_rules = array(
'username' => array(
'not_empty' => NULL,
'min_length' => array(6),
'max_length' => array(250),
'regex' => array('/^[-\pL\pN_#.]++$/uD'),
),
'password' => array(
'not_empty' => NULL,
'min_length' => array(5),
'max_length' => array(30),
),
'password_confirm' => array(
'matches' => array('password'),
),
);
}
class Model_UserAdmin extends Model_User {
protected $_rules = array(
'username' => array(
'not_empty' => NULL,
'min_length' => array(6),
'max_length' => array(250),
'regex' => array('/^[-\pL\pN_#.]++$/uD'),
),
'password' => array(
'not_empty' => NULL,
'min_length' => array(5),
'max_length' => array(42),
),
);
}
In here, Model_UserAdmin extends Model_User and overrides the max length for password and removes the validation for password_confirm (this is not an actual case, but an example).
Is there a better way instead of redefining the entire $_rules property/array?
Use _initialize() instead of __construct($id) if you want to store your UserAdmin model in session (like Auth module does). Serialized ORM objects will not call __construct(), so part of your rules will lost. _initialize() method sets default values for model properties like table_name, relationships etc
protected function _initialize()
{
// redefine model rules
$this->_rules['password']['max_length'] = 42 ;
unset($this->_rules['password_confirm']) ;
// call parent method
parent::_initialize();
}
In the child's constructor you can probably overwrite or add array elements to $this->_rules, as it will already exist as soon as you create a Model_UserAdmin instance.
Specifically, in Model_UserAdmin don't define a protected $rules so it gets it from its parent, and then in the constructor:
$this->_rules['password']['max_length'] = 42 ;
unset($this->_rules['password_confirm']) ;
You can also add some sanity check right before to make sure those keys exist, in case you change them in Model_User and forget.
It's not exactly elegant but it should work. I do suppose you can create some wrapper functions around it (probably in a class ORM extends ORM_Core so they're available when you extend ORM) that modify the rules in a more formal way.
edit please look at biakaveron's answer for a tip on where to place the child rules (_initialize() instead of the constructor)

Categories