OK. here is what I'm trying to do:
class Image{
public $_image;
public $_extension;
public $_mime;
public $_size;
public $_location;
public $_description;
public function __construct($image, $location){
$this->_image = $image;
$this->_location = $location;
$this->_extension = getExtension();
$this->_mime = getMime();
$this->_size = getSize();
}
private functions fallow.....
}
But I keep getting an internal server error when I try to run it. When I comment out the method calls it works. So the question is can I call methods from inside the constructor or am I doing something wrong with the methods.
Do your functions getExtension, getMime and getSize exist? Are they methods on this class? If they are methods, they need to be called with $this->... as in
$this->_extension = $this->getExtension();
If they are not methods, and are functions, you need to make sure the files that contain/define them are loaded before you run the constructor.
Well ..this fragment of code will work as expected:
class Foo
{
protected $secret = null;
public function __construct( $data )
{
$this->secret = $this->makeSecret($data);
}
public function makeSecret( $data )
{
return md5( $data );
}
}
$bar = new Foo( 'lorem ipsum' );
That is not a problem.
But you should know, that is considered to be a bad practice - to do computation/work in the constructor. It makes that class practically untestable. Instead, if you need to perform some computation before "releasing" the object to the rest of the code, you should use a factory. Something along the lines of :
class ImageFactory
{
public function build($image, $location)
{
$instance = new Image($image, $location);
$instance->prepare();
return $instance;
}
}
The class would need some changes:
class Image
{
protected $_image; // you were leaking abstraction
protected $_extension;
protected $_mime;
protected $_size;
protected $_location;
protected $_description;
public function __construct($image, $location)
{
$this->_image = $image;
$this->_location = $location;
}
public function prepare()
{
$this->_extension = $this->getExtension();
$this->_mime = $this->getMime();
$this->_size = $this->getSize();
}
private functions fallow.....
}
Now when you need to create new object you do:
$factory = new ImageFactory;
$image = $factory->build( $file, '/uploads/' );
Of course the instance of ImageFactory can be reusable, and if all your images use the same $location, then you would pass that variable to factory at the initialization. And the factory would be able to "remember it" and pass to all the images it creates:
$factory = new ImageFactory('/uploads/');
$img1 = $factory->build( $file );
$img2 = $factory->build( $something_else );
This is actually how one should deal with creating multiple objects, which all need access to same DB connection instance.
Yes, you can call methods from within the constructor. Remember that the __construct() magic method was implemented in PHP 5. Prior to that, you created a function named the same as your class which acted as your constructor so depending on your PHP version, that could be a problem.
Additionally, the function calls you are making, are they in the class or external? If they are inside the class you need to call them this way:
$this->_extension = $this->getExtension();
You didnt specified what error you are expiriencing clearly. But try calling you class methods even inside the class using this keyword, otherwise it would not work:
public function __construct($image, $location)
{
$this->_image = $image;
$this->_location = $location;
$this->_extension = $this->getExtension();
$this->_mime = $this->getMime();
$this->_size = $this->getSize();
}
Would be a better idea to post your code for the methods you wrote. There could be something wrong within them as well. Possibly forgetting to return a result or something...
Related
class KD_DB extends PDO {
protected static $dbOne = '';
protected static $dbTwo = '';
public function setVariable ($alias,$content){
switch($alias){
case'one': self::$dbOne = $content; break;
case'two': self::$dbTwo = $content; break;
}
}
}
Is there a way to create these dynamically?
Something like this to create the protected static variables
public function setVariable ($alias,$content){
self::${$alias} = $content;
}
It did not work, but I suspect it is because I need it to be static to make it to work with a third class that extends this one...
If you only have the two variables, it may be easier (with more appropriate names) to set them using a static function for each one, something like...
class KD_DB {
public static $dbOne = '';
public static $dbTwo = '';
public static function setOne ($content){
self::$dbOne = $content;
}
}
KD_DB::setOne("value for one");
var_dump(KD_DB::$dbOne);
(code with minor changes to show the process)
But if you wanted a more open ended method, I would go for an associative array as the static variables and then use the 1 method (like you currently are) to set the value in the array...
class KD_DB {
public static $data = [];
public static function setVariable ($alias,$content){
self::$data[$alias] = $content;
}
}
KD_DB::setVariable("three", "value for three");
var_dump(KD_DB::$data);
this method can have issues if you mistype a variable reference which should be found during testing though, but does offer flexibility.
I am creating my own MVC framework, just to learn something new and ran into this problem recently.
Lets say I have like this:
Class Post extends \application\BaseClass
{
private $objPostDetail = null;
private $objAuthor = null
private $objCategory = null;
private $objType = null;
private $objTitlePicture = null;
public function __construct(PostDetail $objPostDetail,
Author $objAuthor,
Category $objCategory,
Type $objType,
TitlePicture $objTitlePicture)
{
$this->objPostDetail = $objPostDetail;
$this->objAuthor = $objAuthor;
$this->objCategory = $objCategory;
$this->objType = $objType;
$this->objTitlePicture = $objTitlePicture;
}
}
Then some objects used in the constuctor can also be comlex to create.
I get data for it from PostDAO class, which returns array of data.
Now the problem is how to create new instance of this class, since it may be on many places in the application.
I think create everywhere $objAuthor, then $objCategory etc. to finally create $objPost is not good. So I created what I think may be called a Factory:
Class PostFactory extends \application\BaseFactory
{
private $arrData = null;
private $objPostDetail = null;
private $objCategory = null;
private $objType = null;
private $objTitlePicture = null;
public function __construct($arrData)
{
$this->arrData = $arrData;
}
public function build()
{
$this->objPostDetail = $this->buildPostDetail();
$this->objCategory = $this->buildCategory();
$this->objType = $this->buildType();
$this->objTitlePicture = $this->buildTitlePicture();
return $this->buildPost();
}
private function buildPostDetail()
{
$objPostDetail = new \includes\classes\factories\PostDetailFactory($this->arrData);
return $objPostDetail->build();
}
private function buildCategory()
{
$objCategory = new \includes\classes\factories\CategoryFactory($this->arrData);
return $objCategory->build();
}
private function buildType()
{
$objType = new \includes\classes\factories\TypeFactory($this->arrData);
return $objType->build();
}
private function buildTitlePicture()
{
$objTitlePicture = new \includes\classes\factories\TitlePictureFactory($this->arrData);
return $objTitlePicture->build();
}
private function buildPost()
{
return new \includes\classes\Post($this->objPostDetail, $this->objCategory,
$this->objType, $this->objTitlePicture);
}
}
It works well, but I don't like that I have twice as much classes and I don't know what parameters do I need for instantiating Post since I pass array to the Factory class (because I want to avoid many parameters in the constructor).
So, my question is what is the best way how to create an instance of class like this?
Thanks in advance for any help.
If you want to create your own MVC framework, I strongly suggest starting with some sort of "Container" which holds instances of service classes (classes that only have to be initialized once, for example Request or Response).
Use reflection classes to automatically inject the parameters a constructor needs by iterating over the function arguments of the constructor.
See the following example of an idea I usually use. It's very reliable and reasonably fast. If your framework has a LOT of different classes and you depend on this functionality a lot, I strongly recommend implementing some way of caching the parameter lists from the reflection classes.
<?php
class SomeClass
{
public function __construct(Request $request, Response $response, $title = '')
{
echo get_class($request);
// will output "Request"
echo $title;
// will output "Hello World"
}
}
// This function will handle the dynamic dependency injection to make sure the constructor gets the arguments passed that it needs, with optional named arguments passing.
$some_class = YourFrameworkDispatcherClass->createInstance('SomeClass', array('title' => 'Hello World'));
?>
I've actually written a blog post about this on my blog. http://harold.info/engineering/php-dynamic-dependency-injection/
I think this can help you out with this structural problem.
I have stored the class I want to instantiate in a variable that is accessible via get and set methods:
private $myClass;
public function setClass($myClass) {$this->myClass = $myClass;}
public function getClass() {return $this->myClass;}
Now later on I want to instantiate this class using the get method:
$instance = new $getClass()();
And also work with some of it's static attributes:
$staticAttr = $getClass()::$attr;
But both of these lines throw errors, I think I've found a solution but I'm really not certain and I feel that my problem is some fundamental lack of understand about how to do this. Or perhaps it is just awful practice and so highly discouraged? How best should I go about approaching this?
PHP's syntax does not allow for this. In order to get this working, you just need to store the class name to a variable first:
$class_name = $obj->getClass();
$instance = new $class_name();
The same goes for accessing the static property:
$class_name = $obj->getClass();
$staticAttr = $class_name::$attr;
This is wrong
$instance = new $getClass()();
Instead do this
$getClass = "classname";
$instance = new $getClass();
for static functions do this
$getClass::getMethod();
For static members
$getClass::$attr;
Tweaking your code to a working object:
class someObject {
private $myClass;
public function setClass($myClass) {$this->myClass = $myClass;}
public function getClass() {return $this->myClass;}
}
Sample
$a = new someObject();
$a->setClass(new stdClass());
var_dump($a->getClass());
You can access $a->getClass()->property, for static properties:
class Foo {
public static $a = 'bar';
}
$a->setClass(new Foo());
$class = $a->getClass();
var_dump($class::$a);
I try to extend the CheckfrontAPI class with my new class.
In my case I use the Singleton pattern in order to load only one instance at a time of my class and I get that error
Fatal error: Declaration of CheckFrontIntegrator::store() must be compatible with that of CheckfrontAPI::store() in /home/my_web_site/public_html/wp-content/plugins/checkfront/class/Checkfront_Integration.php on line 83
Any idea on how to solve that issue ?
Here is the CheckfrontAPI source code : https://github.com/Checkfront/PHP-SDK/blob/master/lib/CheckfrontAPI.php
And here is my class that extends that class:
<?php
class CheckFrontIntegrator extends CheckfrontAPI
{
private static $instance = null;
public $tmp_file = '.checkfront_oauth';
final protected function store($data = array())
{
$tmp_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR. $this->tmp_file;
if(count($data))
{
file_put_contents(
$tmp_file,
json_encode(
$data,
true
)
);
}
elseif(is_file($tmp_file))
{
$data = json_decode(
trim(
file_get_contents(
$tmp_file
)
),
true
);
}
return $data;
}
public function session($session_id, $data = array())
{
$_SESSION['checkfront']['session_id'] = $session_id;
}
public static function instance($data)
{
if(!isset(self::$instance))
{
self::$instance = new CheckFrontIntegrator($data);
}
return self::$instance;
}
public function __construct($data)
{
if(session_id() == '')
{
session_start();
}
parent::__construct($data, session_id());
}
}
?>
And I initiate the new instance of that class like that:
$this->checkfront_integrator = CheckFrontIntegrator::instance($args);
where args are all the important information needit by the class to initiate a new object
AFTER EDIT
I have change my method store from:
final protected function store($data = array())
....
to
protected function store($data)
....
and the problem still occure :(
CheckfrontAPI is an abstract class? in this case your CheckFrontIntegrator::store() arguments count must be identical to original declaration
EDIT
I see on github
abstract protected function store($data);
your override must be:
protected function store($data) {
}
You are extending CheckfrontAPI. CheckfrontAPI has a method store(). If you override that method you must do it properly.
Post the code of CheckfrontAPI and your class Checkfront_Integration: when can understand what's the problem.
When you want to extent the functionality of an existing class by writing your own class and the class you are extending is is an abstract one, you'll need to make sure that the function calls are compatible.
What does this mean?
If the class you are extending has this function call for example :
function walk($direction, $speed = null);
Then you will have to honor the function signature in your implementation - that means you'll still have to have to pass two function arguments in your version.
You will not be able to alter is to be like this :
function walk($direction, $speed, $clothing);
Writing unit tests for code which is already written is fun sometimes.
I am writing a test case for the following code (an example):
<?php
class mockPrivate {
public static function one($a){
$var = static::_two($a);
return $var;
}
private static function _two($a){
return $a+1;
}
}
?>
The test class is like this:
<?php
require_once 'mockPvt.php';
class mockPrivate_test extends PHPUnit_Framework_TestCase {
public $classMock;
protected function setUp(){
$this->classMock = $this->getMock('mockPrivate', array('_two'));
}
public function test_one(){
$a = 1;
$retVal = 2;
$classmock = $this->classMock;
$classmock::staticExpects($this->once())
->method('_two')
->with($a)
->will($this->returnValue($retVal));
$value = $classmock::one($a);
$this->assertEquals($value, $retVal);
}
}
?>
After running by $ phpunit mockPrivate_test.php I got this error:
PHP Fatal error: Call to private method Mock_mockPrivate_531a1619::_two() from context 'mockPrivate' in /data/www/dev-sumit/tests/example
s/mockPvt.php on line 6
But if I change the
private static function _two()
to
public static function _two() or
protected static function _two()
it works totally fine. Since this is a legacy code I can't change the private to public/protected. So is there any way I can test the function one or Is this a limitation of phpunit?
Another option is to create a class that extends mockPrivate, allowing accessibility to the object you wish to test. Your engineers should be thinking long and hard about why something is private (because that means the class is not easily extensible). Also remember that you can mock the test class if you need to override what it returns.
class Test_MockPrivate extends MockPrivate
{
/**
* Allow public access to normally protected function
*/
public static function _two($a){
return parent::_two($a);
}
}
// Code to force the return value of a now public function
$mock = $this->getMock('Test_MockPrivate', array('_two'));
$mock->expects($this->any())
->method('_two')
->will($this->returnValue('Some Overridden Value');
You can use reflection for changing visibility of methods. You can find more info in
PHP object, how to reference?
Use mock and reflection... (posted this solution, since this is the top google result)
$oMock = $this->getMock("Your_class", array('methodToOverride'));
$oMock->expects( $this->any() )
->method('methodToOverride')
->will( $this->returnValue( true ) );
$oReflection = new ReflectionClass("Your_Class");
$oMethod = $oReflection->getMethod('privateMethodToInvoke');
$oMethod->setAccessible( true );
$oMethod->invoke( $oMock );