I have a class called TestClass which has a property called parent. The parent property is the same type as the TestClass.
Show below is a portion of the TestClass I am trying to test.
class TestClass
{
/**
* #param array $parentIds
* #return ApiLock[]
*/
protected function getParentLocks(array $parentIds)
{
if ($this->getParent()) {
$id = array_pop($parentIds);
return $this->getParent()->getLocks($id, $parentIds);
}
return [];
}
/**
* #param array $ids
* #return ApiLock[]
*/
protected function getLocks($id, array $ids)
{
$locks = $this->getParentLocks($ids);
if ($this->config->getLockName()) {
$locks[] = new ApiLock($this->config->getLockName() . '.' . $id, $this->config->getLockWait());
}
return $locks;
}
}
The method getLocks calls the getParentLocks method. To test this class, I have mocked TestClass object and set as the parent of the SUT. Something similar to this.
$apiLock = $this->getMockBuilder(ApiLock::class)
->disableOriginalConstructor()
->getMock();
$parent = $this->getMockBuilder(TestClass::class)
->disableOriginalConstructor()
->getMock();
$parent->expects($this->any())
->method('getLocks')
->will($this->returnValue([$apiLock]));
$testClass->setParent($parent);
But when i run the tests , the method setLocks on the parent object will not return the stubbed value but will actually call the setLocks method on the parent object and the test fails. May be I am not seeing something obvious.
The stubbed method should be called instead of the real method on the parent object.
Please help me thanks in advance.
The problem doesn't have anything to do with the parent-child relation.
You should define which methods you want to mock:
$parent = $this->getMockBuilder(TestClass::class)
->setMethods(['setLocks', 'getLocks'])
->disableOriginalConstructor()
->getMock();
Methods which are not mocked will be called regularly, and your expectations are ignored. You are probably using PHPUnit 4, because in PHPUnit 5 you would see in a warning that you defined an expectation for a method that is not mocked.
Update: It's true that if you don't call setMethods(), all methods should be mocked, but this only applies to public non-abstract methods.
This is the code in PHPUnit_Framework_MockObject_Generator that automatically determines methods to be mocked:
public function getClassMethods($className)
{
$class = new ReflectionClass($className);
$methods = [];
foreach ($class->getMethods() as $method) {
if ($method->isPublic() || $method->isAbstract()) {
$methods[] = $method->getName();
}
}
return $methods;
}
So you can mock protected methods (and also nonexistent methods that would trigger __call()), but other than public methods, you need to specify them explicitly.
I'm trying to get my head around the OOP principles and coding my own classes. As a means to learn, I have decided to convert a couple of functions I have written in Wordpress to OOP classes. These functions work together in order to output the correct post links on single pages according to referrers (4 of them) set in the URL.
This is the setup with a basic workflow (The workflow can change as I go along):
4 query variables are set to the URL according to archive page, ie, one query variable for taxonomy pages, one query variable set for the author pages and so one. No page can ever have more than one custom query variable. This 4 variables is retrieved by my first class and checked against a given global variable, in this case $_GET. I have not hardcoded the 4 variables in my class, and this goes for $_GET as well to keep the class testable. If the value exists in the URL, the key/value pair is returned through the has* methods. These methods return null if no match is found. (this is raw data which will be sanitized/escaped by the classes that will use this data)
Here is the full class
<?php
namespace PG\Single\Post\Navigation;
/**
* Test set values against the super global given. Returns conditional properties
* which is boolean values. true is returned on success and false on failure.
*
* #param $superGlobalVar Super global to test the values against
* #param (string) $authorReferrer
* #param (string) $dateReferrer
* #param (string) $searchReferrer
* #param (string) $taxReferrer
*/
class RequestReferrerHandler implements RequestReferrerHandlerInterface
{
/**
* #since 1.0.0
* #access protected
* #var (array) $superGlobalVar
*/
protected $superGlobalVar;
/**
* #since 1.0.0
* #access protected
* #var (string) $authorReferrer
*/
protected $authorReferrer;
/**
* #since 1.0.0
* #access protected
* #var (string) $dateReferrer
*/
protected $dateReferrer;
/**
* #since 1.0.0
* #access protected
* #var (string) $searchReferrer
*/
protected $searchReferrer;
/**
* #since 1.0.0
* #access protected
* #var (string) $taxReferrer
*/
protected $taxReferrer;
/**
* Public constructor method.
*
* #param $superGlobalVar Super global to get data from
* #param $authorReferrer Query variable from author referrer to test
* #param $dateReferrer Query variable from date referrer to test
* #param $searchReferrer Query variable from search referrer to test
* #param $taxReferrer Query variable from taxonomy referrer to test
*/
public function __construct($superGlobalVar = null, $authorReferrer= null, $dateReferrer = null, $searchReferrer = null, $taxReferrer = null)
{
$this->superGlobalVar = $superGlobalVar;
$this->authorReferrer = $authorReferrer;
$this->dateReferrer = $dateReferrer;
$this->searchReferrer = $searchReferrer;
$this->taxReferrer = $taxReferrer;
}
/**
* Setter setSuperGlobalVar.
*
* #since 1.0.0
* #param $superGlobalVar
* #return $this
*/
public function setSuperGlobalVar($superGlobalVar)
{
$this->superGlobalVar = $superGlobalVar;
return $this;
}
/**
* Returns an array of super global variables.
*
* #since 1.0.0
* #return (array) $this->superGlobalVar
*/
public function getSuperGlobalVar()
{
return $this->superGlobalVar;
}
/**
* Setter setAuthorReferrer
*
* #since 1.0.0
* #param $authorReferrer
* #return $this
*/
public function setAuthorReferrer($authorReferrer)
{
$this->authorReferrer = $authorReferrer;
return $this;
}
/**
* Returns the value of the $authorReferrer property.
*
* #since 1.0.0
* #return (array) $this->authorReferrer
*/
public function getAuthorReferrer()
{
return $this->authorReferrer;
}
/**
* Setter setDateReferrer.
*
* #since 1.0.0
* #param $dateReferrer
* #return $this
*/
public function setDateReferrer($dateReferrer)
{
$this->dateReferrer = $dateReferrer;
return $this;
}
/**
* Returns the value of the $dateReferrer property.
*
* #since 1.0.0
* #return (array) $this->dateReferrer
*/
public function getDateReferrer()
{
return $this->dateReferrer;
}
/**
* Setter setSearchReferrer.
*
* #since 1.0.0
* #param $searchReferrer
* #return $this
*/
public function setSearchReferrer($searchReferrer)
{
$this->searchReferrer = $searchReferrer;
return $this;
}
/**
* Returns the value of the $searchReferrer property.
*
* #since 1.0.0
* #return (array) $this->searchReferrer
*/
public function getSearchReferrer()
{
return $this->searchReferrer;
}
/**
* Setter setTaxReferrer.
*
* #since 1.0.0
* #param $taxReferrer
* #return $this
*/
public function setTaxReferrer($taxReferrer)
{
$this->taxReferrer = $taxReferrer;
return $this;
}
/**
* Returns the value of the $taxReferrer property.
*
* #since 1.0.0
* #return (array) $this->taxReferrer
*/
public function getTaxReferrer()
{
return $this->$taxReferrer;
}
/**
* Test $authorReferrer against $superGlobalVar.
*
* #since 1.0.0
* #return (bool) true on success or false on failure
*/
public function isAuthorReferrer()
{
if ($this->authorReferrer && isset($this->superGlobalVar[$this->authorReferrer])) {
$isAuthorReferrer = true;
} else {
$isAuthorReferrer = false;
}
return $isAuthorReferrer;
}
/**
* Test $authorReferrer against $superGlobalVar
*
* #since 1.0.0
* #return (bool) true on success or false on failure
*/
public function isDateReferrer()
{
if ($this->dateReferrer && isset($this->superGlobalVar[$this->dateReferrer])) {
$isDateReferrer = true;
} else {
$isDateReferrer = false;
}
return $isDateReferrer;
}
/**
* Test $authorReferrer against $superGlobalVar.
*
* #since 1.0.0
* #return (bool) true on success or false on failure
*/
public function isSearchReferrer()
{
if ($this->searchReferrer && isset($this->superGlobalVar[$this->searchReferrer])) {
$isSearchReferrer = true;
} else {
$isSearchReferrer = false;
}
return $isSearchReferrer;
}
/**
* Test $authorReferrer against $superGlobalVar.
*
* #since 1.0.0
* #return (bool) true on success or false on failure
*/
public function isTaxReferrer()
{
if ($this->taxReferrer && isset($this->superGlobalVar[$this->taxReferrer])) {
$isTaxReferrer = true;
} else {
$isTaxReferrer = false;
}
return $isTaxReferrer;
}
/**
* Conditional which check if the current post is a referred post.
*
* #since 1.0.0
* #return (bool) true on success or false on failure
*/
public function isReferredPost()
{
if ($this->isAuthorReferrer() || $this->isDateReferrer() || $this->isSearchReferrer() || $this->isTaxReferrer()) {
$isReferredPost = true;
} else {
$isReferredPost = false;
}
return $isReferredPost;
}
/**
* Return the value from the super global when the current post is a post referred from
* an author archive page.
*
* #since 1.0.0
* #return (array) $authorReferrerValue
*/
public function hasAuthorReferrerValue()
{
if ($this->isAuthorReferrer()) {
$authorReferrerValue = [$this->authorReferrer => $this->superGlobalVar[$this->authorReferrer]];
} else {
$authorReferrerValue = null;
}
return $authorReferrerValue;
}
/**
* Return the value from the super global when the current post is a post referred from
* a date archive page.
*
* #since 1.0.0
* #return (array) $dateReferrerValue
*/
public function hasDateReferrerValue()
{
if ($this->isDateReferrer()) {
$dateReferrerValue = [$this->dateReferrer => $this->superGlobalVar[$this->dateReferrer]];
} else {
$dateReferrerValue = null;
}
return $dateReferrerValue;
}
/**
* Return the value from the super global when the current post is a post referred from
* a search page.
*
* #since 1.0.0
* #return (array) $searchReferrerValue
*/
public function hasSearchReferrerValue()
{
if ($this->isSearchReferrer()) {
$searchReferrerValue = [$this->searchReferrer => $this->superGlobalVar[$this->searchReferrer]];
} else {
$searchReferrerValue = null;
}
return $searchReferrerValue;
}
/**
* Return the value from the super global when the current post is a post referred from
* a taxonomy archive page.
*
* #since 1.0.0
* #return (array) $taxReferrerValue
*/
public function hasTaxReferrerValue()
{
if ($this->isTaxReferrer()) {
$taxReferrerValue = [$this->taxReferrer => $this->superGlobalVar[$this->taxReferrer]];
} else {
$taxReferrerValue = null;
}
return $taxReferrerValue;
}
}
This is how I use this class
$b = new RequestReferrerHandler($_GET, 'aq', 'dq', 'sq', 'tq');
?><pre><?php var_dump($b->hasAuthorReferrerValue()); ?></pre><?php
?><pre><?php var_dump($b->hasDateReferrerValue()); ?></pre><?php
?><pre><?php var_dump($b->hasSearchReferrerValue()); ?></pre><?php
?><pre><?php var_dump($b->hasTaxReferrerValue()); ?></pre><?php
For testing purposes you can inject something like ['aq' => '1'] into the class instead of $_GET
This is where I'm stuck now and have no idea how to move on. I need to construct two classes which will both use the same methods from the class above, one class that will construct query arguments from the has* methods from the above class, and one class will create query_vars also from the has* methods from the above class that will be used to construct new post links
So, in short, both classes will make use of the exact same for methods from the above class
hasAuthorReferrerValue();
hasDateReferrerValue();
hasSearchReferrerValue();
hasTaxReferrerValue();
Just as an example, here is an example of how the two classes should look like. (I have omitted some of the methods here to make the code more manageable)
ClassA
<?php
namespace PG\Single\Post\Navigation;
class ClassA //Just a generic name for testing purposes. Will also implement ClassAInterface
{
protected $handler;
public function __construct(RequestReferrerHandlerInterface $handler)
{
$this->handler = $handler;
}
public function santizeAuthor()
{
$author = $this->handler->hasAuthorReferrerValue(); // Value will be either null or single key/value pair array. Example ['aq' => '1']
if ($author) {
$author = array_values($author);
$author = ['author' => (int)htmlspecialchars($author[0])]; //Will output ['author' => 1]
}
return $author; //Returns null or the array ['author' => 1]
}
public function santizeDate()
{
$date = $this->handler->hasDateReferrerValue();
if ($date) {
// #TODO Still to work out
}
return $date;
}
//etc
public function queryArguments() // Will be used in the controller class ClassC
{
$queryArgs = null;
if ($this->santizeAuthor()) {
$queryArgs = $this->santizeAuthor();
} elseif ($this->santizeDate) {
$queryArgs = $this->santizeDate();
} // etc
return $queryArgs; //Will return null if all 4 conditions fail or return the value from the one that returns true
}
}
ClassB
<?php
namespace PG\Single\Post\Navigation;
class ClassB //Just a generic name for testing purposes. Will also implement ClassBInterface
{
protected $handler;
public function __construct(RequestReferrerHandlerInterface $handler)
{
$this->handler = $handler;
}
public function santizeAuthor()
{
$author = $this->handler->hasAuthorReferrerValue(); // Value will be either null or single key/value pair array. Example ['aq' => '1']
if ($author) {
foreach ($author as $k=>$v)
$author[htmlspecialchars($k)] = (int)htmlspecialchars($v);
}
return $author; //Returns null or the array ['aq' => 1]
}
public function santizeDate()
{
$date = $this->handler->hasDateReferrerValue();
if ($date) {
// #TODO Still to work out
}
return $date;
}
//etc
public function queryVars() // Will be used in the controller class ClassC
{
$queryVars = null;
if ($this->santizeAuthor()) {
$queryVars = $this->santizeAuthor();
} elseif ($this->santizeDate) {
$queryVars = $this->santizeDate();
} // etc
return $queryVars; //Will return null if all 4 conditions fail or return the value from the one that returns true
}
}
The queryArguments() method from ClassA and the queryVars() method from ClassB will be used in other classes (or one controller class)
My total lack of proper knowledge coming into OOP, confusion with separation of concerns, encapsulation, SOLID principles and keeping class testable have me second guessing my code, and I do feel I am missing something.
Is there anyway I can optimize the above. I am not asking for any type of code rewritting, all I need is proper pointers and ideas on optimizing this to bring it up to standard if it is not. It would be a real plus if anyone can give code samples, something like an outline skeleton
Looking over your code you are definitely off to a good start. You are already using one good rule of thumb when programming in OOP - program to an interface, not an implementation. By the term interface I'm not referring only to actual interfaces, but abstract classes as well.
So at the heart of your question you want to have two classes, ClassA and ClassB that both use common methods from RequestReferrerHandler. You already have the ground work laid out to do that with your interface RequestReferrerHandlerInterface. So we'll say that you have an interface that looks like this:
interface RequestReferrerHandlerInterface
{
public function hasAuthorReferrerValue();
public function hasDateReferrerValue();
public function hasSearchReferrerValue();
public function hasTaxReferrerValue();
}
So long as this interface is implemented by the RequestReferrerHandler you can type hint the interface as the constructor requirements for ClassA and ClassB. But this isn't anything new because you were already doing this.
There are two things in particular that stand out at me as potential sore thumbs. First, since you want the responsibilities of your classes to be small, you should take the responsibility of providing data to the RequestReferrerHandler away from itself and give it to your Controller. In other words, do not inject $_GET into your class. Make sure your Controller has all the information it needs to properly create the RequestReferrerHandler Let's take a look at your RequestReferrerHandler class, flushed with all of the methods it will need.
class RequestReferrerHandler implements RequestReferrerHandlerInterface
{
private $author;
private $date;
private $search;
private $tax;
public function __construct($author = null, $date = null, $search = null, $tax = null)
{
$this->setAuthorReferrer($author);
$this->setDateReferrer($date);
$this->setSearchReferrer($search);
$this->setTaxReferrer($tax);
}
public function hasAuthorReferrerValue()
{
return $this->author !== null ? true : false;
}
public function hasDateReferrerValue()
{
return $this->date !== null ? true : false;
}
public function hasSearchReferrerValue()
{
return $this->search !== null ? true : false;
}
public function hasTaxReferrerValue()
{
return $this->tax !== null ? true : false;
}
public function getAuthorReferrer()
{
return $this->author;
}
public function getDateReferrer()
{
return $this->date;
}
public function getSearchReferrer()
{
return $this->search;
}
public function getTaxReferrer()
{
return $this->tax;
}
public function setAuthorReferrer($author)
{
$this->author = $author;
}
public function setDateReferrer($date)
{
$this->date = $date;
}
public function setSearchReferrer($search)
{
$this->search = $search;
}
public function setTaxReferrer($tax)
{
$this->tax = $tax;
}
}
The second thing that sticks out is the santize() methods. Do you see how they are duplicated in both ClassA and ClassB? The sanitizeAuthor() is different among the two classes, but how about the rest? This is a case where the DRY (Don't Repeat Yourself) principle can help out. Since multiple classes may have to sanitize data in similar ways it make sense to abstract that away from your classes.
Let's take a look at how to do that and then we will come back to your concrete classes. First create a new interface that will specify methods that must be exposed by an object that can sanitize data.
interface SanitizerInterface
{
public function sanitizeAuthor();
public function sanitizeDate();
public function sanitizeSearch();
public function sanitizeTaxonomy();
}
Now, if every object you had of ClassX implemented these four methods in different ways you could start implementing it in different classes that simply sanitize data. However, for this example we will say that is not the case. Let's make the assumption that sanitizeAuthor() could be different among ClassA and ClassB (which it is in your code) and all the other methods will be implemented exactly the same. This is case where we can use an abstract class that will implement the sanitizer methods.
abstract class AbstractSanitizer implements SanitizerInterface
{
protected $handler;
public function __construct() {}
public function setHandler(RequestReferrerHandlerInterface $handler)
{
$this->handler = $handler;
}
/* For this example we are saying that sanitizeDate(), sanitizeTaxonomy() and
* sanitizeSearch() will be the same no matter what. So let's implement them
* and leave the child classes to implement sanitizeAuthor().
*
* Implement the details of the sanitizer function to fit your needs.
*/
public function sanitizeDate()
{
if($this->handler !== null)
{
//Perform whatever tasks to sanitize the date
$sanitized = strtoupper($this->handler->getDateReferrer());
echo "Sanitize date -> switch to uppercase letters.\n";
$this->handler->setDateReferrer($sanitized);
}
}
public function sanitizeSearch()
{
if($this->handler !== null)
{
//Perform whatever tasks to sanitize the search
$sanitized = strtolower($this->handler->getSearchReferrer());
echo "Sanitize search -> switch to lowercase letters.\n";
$this->handler->setSearchReferrer($sanitized);
}
}
public function sanitizeTaxonomy()
{
if($this->handler !== null)
{
//Perform whatever tasks to sanitize the taxonomy
$sanitized = str_replace(" ", "_", $this->handler->getTaxReferrer());
echo "Sanitize Taxonomy -> convert spaces to underscores.\n";
$this->handler->setTaxReferrer($sanitized);
}
}
}
Some things to take note of right off the bat. First, you'll notice there is the setHandler() method which accepts an instance of the RequestReferrerHandlerInterface. Why is this there? Convenience for the most part. Since we have taken the sanitizing behavior and encapsulated it into its own class it would be nice it we gave the sanitizer a way to update the concrete RequestReferrerHandler it is using with the updated output from a sanitize method.
Next thing, we are using methods from the RequestReferrerHandler class that are not specified in the RequestReferrerHandlerInterface. This isn't an immediate problem per se, because we know that methods like the getters and setters are in the class. However, type hinting to the interface alone doesn't guarantee that those methods will be available if you ever decided to implement that interface with a different concrete object. Therefore, we need to update the RequestReferrerHandlerInterface with methods that will guarantee their availability.
interface RequestReferrerHandlerInterface
{
public function hasAuthorReferrerValue();
public function hasDateReferrerValue();
public function hasSearchReferrerValue();
public function hasTaxReferrerValue();
public function getAuthorReferrer();
public function getDateReferrer();
public function getSearchReferrer();
public function getTaxReferrer();
public function setAuthorReferrer($author);
public function setDateReferrer($date);
public function setSearchReferrer($search);
public function setTaxReferrer($tax);
}
Now, back to those sanitizers. We know that ClassA and ClassB will implement their sanitizeAuthor() methods differently. The abstract class AbstractSanitizer was made the way it was because the sanitizeAuthor() method from the the SanitizerInteface isn't implemented in AbstractSanitizer so we have to extend it to provide the functionality. We will need the following two classes to do this:
class SanitizerForClassA extends AbstractSanitizer
{
/* This class must provide an implementation for how ClassA will
* handle the sanitizeAuthor() method.
*/
public function sanitizeAuthor()
{
if($this->handler !== null)
{
//Perform whatever tasks to sanitize the for ClassA
$sanitized = array("author" => $this->handler->getAuthorReferrer());
echo "Sanitize author -> ClassA makes author an array.\n";
$this->handler->setAuthorReferrer($sanitized);
}
}
}
class SanitizerForClassB extends AbstractSanitizer
{
/* This class must provide an implementation for how ClassB will
* handle the sanitizeAuthor() method.
*/
public function sanitizeAuthor()
{
if($this->handler !== null)
{
//Perform whatever tasks to sanitize the for ClassB
$sanitized = new stdClass();
$sanitized->author = $this->handler->getAuthorReferrer();
echo "Sanitize author -> ClassB makes author an object property. \n";
$this->handler->setAuthorReferrer($sanitized);
}
}
}
These two concrete classes can be used with ClassA and ClassB to sanitize data within the concrete RequestReferrerHandler methods that will be passed into them.
So moving on, let's look at the spec for ClassA and ClassB. We know that ClassA will need the method queryArguments(), ClassB will need the method queryVars() and both classes will need to allow and instance of a RequestReferrerHandlerInterface and SanitizerInterface in their constructors. We will handle the constructor requirement with one interface, then two other interfaces will extend that to provide all the method requirements needed for ClassA and ClassB.
interface SanitizableHandlerInterface
{
public function __construct(RequestReferrerHandlerInterface $handler, SanitizerInterface $sanitizer);
}
interface QueryVarsInterface extends SanitizableHandlerInterface
{
public function queryVars();
}
interface QueryArgumentsInterface extends SanitizableHandlerInterface
{
public function queryArguments();
}
Since we are now getting down to it, let's take a look at those classes that will use these.
class ClassA implements QueryArgumentsInterface
{
private $handler;
private $sanitizer;
public function __construct(RequestReferrerHandlerInterface $handler, SanitizerInterface $sanitizer)
{
$this->handler = $handler;
$this->sanitizer = $sanitizer;
$this->sanitizer->setHandler($this->handler);
}
public function queryArguments() // Will be used in the controller class ClassC
{
$queryArgs = null;
if($this->handler->hasAuthorReferrerValue())
{
$this->sanitizer->sanitizeAuthor();
$queryArgs = $this->handler->getAuthorReferrer();
}
if($this->handler->hasDateReferrerValue())
{
$this->sanitizer->sanitizeDate();
$queryArgs = $this->handler->getDateReferrer();
}
if($this->handler->hasSearchReferrerValue())
{
$this->sanitizer->sanitizeSearch();
$queryArgs = $this->handler->getSearchReferrer();
}
if($this->handler->hasTaxReferrerValue())
{
$this->sanitizer->sanitizeTaxonomy();
$queryArgs = $this->handler->getTaxReferrer();
}
return $queryArgs; //Will return null if all 4 conditions fail or return the value from the one that returns true
}
}
class ClassB implements QueryVarsInterface
{
private $handler;
private $sanitizer;
public function __construct(RequestReferrerHandlerInterface $handler, SanitizerInterface $sanitizer)
{
$this->handler = $handler;
$this->sanitizer = $sanitizer;
$this->sanitizer->setHandler($this->handler);
}
public function queryVars() // Will be used in the controller class ClassC
{
$queryVars = null;
if($this->handler->hasAuthorReferrerValue())
{
$this->sanitizer->sanitizeAuthor();
$queryVars = $this->handler->getAuthorReferrer();
}
if($this->handler->hasDateReferrerValue())
{
$this->sanitizer->sanitizeDate();
$queryVars = $this->handler->getDateReferrer();
}
if($this->handler->hasSearchReferrerValue())
{
$this->sanitizer->sanitizeSearch();
$queryVars = $this->handler->getSearchReferrer();
}
if($this->handler->hasTaxReferrerValue())
{
$this->sanitizer->sanitizeTaxonomy();
$queryVars = $this->handler->getTaxReferrer();
}
return $queryVars; //Will return null if all 4 conditions fail or return the value from the one that returns true
}
}
There you have it, the ground work is built. You'll notice that in the constructors properties are set for the handler and sanitizer class that was given and then the sanitizer is given the reference to the handler. (Remember, the sanitizers have a reference to the handler so that sanitized properties in the handler are automatically updated. The individual classes don't need to worry about that now.)
So now the million dollar question is how to use this. Well, you need a controller that can accept ClassA and ClassB. We will type hint these by their respective interfaces as well.
class Controller
{
public function __construct() {}
public function doStuff(QueryArgumentsInterface $argsClass, QueryVarsInterface $varsClass)
{
var_dump($argsClass->queryArguments());
var_dump($varsClass->queryVars());
}
}
In your version of queryArguments() and queryVars() you expected sanitized data for a return value. Let's plug some data in and see what we get. (Note: As you already figured out none of the sanitize methods I used are doing what you were doing, they are illustrative only.)
//TEST DRIVE
//Create a controller that will use the classes
$controller = new Controller();
//Now make use of your new shiny handlers and sanitizers
$controller->doStuff(
new ClassA(new RequestReferrerHandler("Mark Twain", null, null, null), new SanitizerForClassA()),
new ClassB(new RequestReferrerHandler(null, "January 1st, 1999", null, null), new SanitizerForClassB())
);
$controller->doStuff(
new ClassA(new RequestReferrerHandler(null, null, "OK Google Now!", null), new SanitizerForClassA()),
new ClassB(new RequestReferrerHandler(null, null, null, "Super Awesome Taxonomy Tables"), new SanitizerForClassB())
);
$controller->doStuff(
new ClassA(new RequestReferrerHandler(null, "January 1st, 1999", null, null), new SanitizerForClassA()),
new ClassB(new RequestReferrerHandler("Mark Twain", null, null, null), new SanitizerForClassB())
);
$controller->doStuff(
new ClassA(new RequestReferrerHandler(null, null, null, "Super Awesome Taxonomy Tables"), new SanitizerForClassA()),
new ClassB(new RequestReferrerHandler(null, null, "OK Google Now!", null), new SanitizerForClassB())
);
Here's the output:
Sanitize author -> ClassA makes author an array.
array (size=1)
'author' => string 'Mark Twain' (length=10)
Sanitize date -> switch to uppercase letters.
string 'JANUARY 1ST, 1999' (length=17)
Sanitize search -> switch to lowercase letters.
string 'ok google now!' (length=14)
Sanitize Taxonomy -> convert spaces to underscores.
string 'Super_Awesome_Taxonomy_Tables' (length=29)
Sanitize date -> switch to uppercase letters.
string 'JANUARY 1ST, 1999' (length=17)
Sanitize author -> ClassB makes author an object property.
object(stdClass)[15]
public 'author' => string 'Mark Twain' (length=10)
Sanitize Taxonomy -> convert spaces to underscores.
string 'Super_Awesome_Taxonomy_Tables' (length=29)
Sanitize search -> switch to lowercase letters.
string 'ok google now!' (length=14)
So what did all of this cost you? Short answer - complexity. It took 4 interfaces, 1 abstract class and a handful of concrete classes to output a little bit of data to the screen.
What do you gain? Short answer - flexibility. In the future you may wish to add more classes that implement either the QueryVarsInterface or QueryArgumentsInterface. Consider these classes ClassC, ClassD and ClassE. All of these classes will need a sanitizer class to go with them (that is if SanitizerForClassA or SanitizerForClassB do not fit the bill) and it would be tedious to keep having to write sanitizer classes. Well, good thing for you, since you were programming to an interface all along, you won't have that problem. You can easily make a GenericSanitizer with a default implementation of the sanitizeAuthor() method. Use can use this class with Controller::doStuff() in any case where you don't need a specialized sanitizer class. You could just as easily implement different concrete classes of QueryArgumentInterface or QueryVarsInterface to test out experimental features you want to add without tampering with your current classes.
Hopefully this has given you some insight on some OOP principles. Here is a complete copy of all of the code above. Slap this in an empty PHP file and run it to see everything in action. Happy programming!
<?php
/*
* INTERFACES
*/
interface RequestReferrerHandlerInterface
{
public function hasAuthorReferrerValue();
public function hasDateReferrerValue();
public function hasSearchReferrerValue();
public function hasTaxReferrerValue();
public function getAuthorReferrer();
public function getDateReferrer();
public function getSearchReferrer();
public function getTaxReferrer();
public function setAuthorReferrer($author);
public function setDateReferrer($date);
public function setSearchReferrer($search);
public function setTaxReferrer($tax);
}
interface SanitizerInterface
{
public function sanitizeAuthor();
public function sanitizeDate();
public function sanitizeSearch();
public function sanitizeTaxonomy();
}
interface SanitizableHandlerInterface
{
public function __construct(RequestReferrerHandlerInterface $handler, SanitizerInterface $sanitizer);
}
interface QueryVarsInterface extends SanitizableHandlerInterface
{
public function queryVars();
}
interface QueryArgumentsInterface extends SanitizableHandlerInterface
{
public function queryArguments();
}
/*
* ABSTRACT CLASSES
*/
abstract class AbstractSanitizer implements SanitizerInterface
{
protected $handler;
public function __construct() {}
public function setHandler(RequestReferrerHandlerInterface $handler)
{
$this->handler = $handler;
}
/* For this example we are saying that sanitizeDate(), sanitizeTaxonomy() and
* sanitizeSearch() will be the same no matter what. So let's implement them
* and leave the child classes to implement sanitizeAuthor().
*
* Implement the details of the sanitizer function to fit your needs.
*/
public function sanitizeDate()
{
if($this->handler !== null)
{
//Perform whatever tasks to sanitize the date
$sanitized = strtoupper($this->handler->getDateReferrer());
echo "Sanitize date -> switch to uppercase letters.\n";
$this->handler->setDateReferrer($sanitized);
}
}
public function sanitizeSearch()
{
if($this->handler !== null)
{
//Perform whatever tasks to sanitize the search
$sanitized = strtolower($this->handler->getSearchReferrer());
echo "Sanitize search -> switch to lowercase letters.\n";
$this->handler->setSearchReferrer($sanitized);
}
}
public function sanitizeTaxonomy()
{
if($this->handler !== null)
{
//Perform whatever tasks to sanitize the taxonomy
$sanitized = str_replace(" ", "_", $this->handler->getTaxReferrer());
echo "Sanitize Taxonomy -> convert spaces to underscores.\n";
$this->handler->setTaxReferrer($sanitized);
}
}
}
/*
* CONCRETE CLASSES
*/
class RequestReferrerHandler implements RequestReferrerHandlerInterface
{
private $author;
private $date;
private $search;
private $tax;
public function __construct($author = null, $date = null, $search = null, $tax = null)
{
$this->setAuthorReferrer($author);
$this->setDateReferrer($date);
$this->setSearchReferrer($search);
$this->setTaxReferrer($tax);
}
public function hasAuthorReferrerValue()
{
return $this->author !== null ? true : false;
}
public function hasDateReferrerValue()
{
return $this->date !== null ? true : false;
}
public function hasSearchReferrerValue()
{
return $this->search !== null ? true : false;
}
public function hasTaxReferrerValue()
{
return $this->tax !== null ? true : false;
}
public function getAuthorReferrer()
{
return $this->author;
}
public function getDateReferrer()
{
return $this->date;
}
public function getSearchReferrer()
{
return $this->search;
}
public function getTaxReferrer()
{
return $this->tax;
}
public function setAuthorReferrer($author)
{
$this->author = $author;
}
public function setDateReferrer($date)
{
$this->date = $date;
}
public function setSearchReferrer($search)
{
$this->search = $search;
}
public function setTaxReferrer($tax)
{
$this->tax = $tax;
}
}
class SanitizerForClassA extends AbstractSanitizer
{
/* This class must provide an implementation for how ClassA will
* handle the sanitizeAuthor() method.
*/
public function sanitizeAuthor()
{
if($this->handler !== null)
{
//Perform whatever tasks to sanitize the for ClassA
$sanitized = array("author" => $this->handler->getAuthorReferrer());
echo "Sanitize author -> ClassA makes author an array.\n";
$this->handler->setAuthorReferrer($sanitized);
}
}
}
class SanitizerForClassB extends AbstractSanitizer
{
/* This class must provide an implementation for how ClassB will
* handle the sanitizeAuthor() method.
*/
public function sanitizeAuthor()
{
if($this->handler !== null)
{
//Perform whatever tasks to sanitize the for ClassB
$sanitized = new stdClass();
$sanitized->author = $this->handler->getAuthorReferrer();
echo "Sanitize author -> ClassB makes author an object property. \n";
$this->handler->setAuthorReferrer($sanitized);
}
}
}
class ClassA implements QueryArgumentsInterface
{
private $handler;
private $sanitizer;
public function __construct(RequestReferrerHandlerInterface $handler, SanitizerInterface $sanitizer)
{
$this->handler = $handler;
$this->sanitizer = $sanitizer;
$this->sanitizer->setHandler($this->handler);
}
public function queryArguments() // Will be used in the controller class ClassC
{
$queryArgs = null;
if($this->handler->hasAuthorReferrerValue())
{
$this->sanitizer->sanitizeAuthor();
$queryArgs = $this->handler->getAuthorReferrer();
}
if($this->handler->hasDateReferrerValue())
{
$this->sanitizer->sanitizeDate();
$queryArgs = $this->handler->getDateReferrer();
}
if($this->handler->hasSearchReferrerValue())
{
$this->sanitizer->sanitizeSearch();
$queryArgs = $this->handler->getSearchReferrer();
}
if($this->handler->hasTaxReferrerValue())
{
$this->sanitizer->sanitizeTaxonomy();
$queryArgs = $this->handler->getTaxReferrer();
}
return $queryArgs; //Will return null if all 4 conditions fail or return the value from the one that returns true
}
}
class ClassB implements QueryVarsInterface
{
private $handler;
private $sanitizer;
public function __construct(RequestReferrerHandlerInterface $handler, SanitizerInterface $sanitizer)
{
$this->handler = $handler;
$this->sanitizer = $sanitizer;
$this->sanitizer->setHandler($this->handler);
}
public function queryVars() // Will be used in the controller class ClassC
{
$queryVars = null;
if($this->handler->hasAuthorReferrerValue())
{
$this->sanitizer->sanitizeAuthor();
$queryVars = $this->handler->getAuthorReferrer();
}
if($this->handler->hasDateReferrerValue())
{
$this->sanitizer->sanitizeDate();
$queryVars = $this->handler->getDateReferrer();
}
if($this->handler->hasSearchReferrerValue())
{
$this->sanitizer->sanitizeSearch();
$queryVars = $this->handler->getSearchReferrer();
}
if($this->handler->hasTaxReferrerValue())
{
$this->sanitizer->sanitizeTaxonomy();
$queryVars = $this->handler->getTaxReferrer();
}
return $queryVars; //Will return null if all 4 conditions fail or return the value from the one that returns true
}
}
class Controller
{
public function __construct() {}
public function doStuff(QueryArgumentsInterface $argsClass, QueryVarsInterface $varsClass)
{
var_dump($argsClass->queryArguments());
var_dump($varsClass->queryVars());
}
}
/*
* TEST DRIVE
*/
//Create a controller that will use the classes
$controller = new Controller();
//Now make use of your new shiny handlers and sanitizers
$controller->doStuff(
new ClassA(new RequestReferrerHandler("Mark Twain", null, null, null), new SanitizerForClassA()),
new ClassB(new RequestReferrerHandler(null, "January 1st, 1999", null, null), new SanitizerForClassB())
);
$controller->doStuff(
new ClassA(new RequestReferrerHandler(null, null, "OK Google Now!", null), new SanitizerForClassA()),
new ClassB(new RequestReferrerHandler(null, null, null, "Super Awesome Taxonomy Tables"), new SanitizerForClassB())
);
$controller->doStuff(
new ClassA(new RequestReferrerHandler(null, "January 1st, 1999", null, null), new SanitizerForClassA()),
new ClassB(new RequestReferrerHandler("Mark Twain", null, null, null), new SanitizerForClassB())
);
$controller->doStuff(
new ClassA(new RequestReferrerHandler(null, null, null, "Super Awesome Taxonomy Tables"), new SanitizerForClassA()),
new ClassB(new RequestReferrerHandler(null, null, "OK Google Now!", null), new SanitizerForClassB())
);
As I can see in your previous questions, you look for a way to rationalize your OOP development. This is why I won't give you a fish but I will help you to fish by yourself. This means that I'm gonna (try to) give the base that you should know to do a strong OOP code.
1. SRP and composition
As I can see in your questions, you try to separate responsability of your classes. This is a good thing in OOP of course. What you do is called Single Responsability Principle (SRP). This principle imply that you prefer composition over inheritance.
// Composition
class Car implements VehicleInterface
{
private $motor;
}
class Motor implements MotorInterface
In this case, the car and the motor have 2 different responsabilities.
// Inheritance
class Car extends MotorVehicle
{
}
In the case of inheritance, you make a high compling between the vehicle and motor notions.
Imagine I want to had a new notion like the movement for example:
// Composition
class Car implements VehicleInterface
{
private $motor;
private $movement;
}
class Motor implements MotorInterface
class Drive implements MovementInterface
No problem with composition.
// Inheritance
class Car extends MotorVehicle, DriveVehicle
{
}
Multiple inheritance is bad (and even not possible in PHP) because you break the SRP. Inheritance should only be used for factoring code for classes of the same responsability. As you should have only one responsability by class, you shouldn't use multiple inheritance. Other possibilities are bad because you cannot tell me that a Car is more a MotorVehicle than a DriveVehicle.
In your case, you have some request referrer handlers and some sanitizer.
2. Interfaces and low coupling
As I told you in my previous answer your do right when using interfaces to make a low coupling between your classes. This give you a more maintenable, scalable and testable code. Take the previous example:
class Car implements VehicleInterface
{
private $motor;
}
class PetrolMotor implements MotorInterface
class DieselMotor implements MotorInterface
Your car can easily take different kind of motors now!
The idea that should drive your mind here, is that a class should never use another class directly but an interface describing a behaviour.
In your case, class A and class B should implement an interface SanitizerInterface.
3. Dependency Injection
At this point, you want to set your handlers in your sanitizer. The better way is to use dependency injection. This is really simple!
class Car implements VehicleInterface
{
private $motor;
public function __construct(MotorInterface $motor)
{
$this->motor = $motor;
}
}
class PetrolMotor implements MotorInterface
{
}
class DieselMotor implements MotorInterface
{
}
$motor = new PetrolMotor();
$car = new Car($motor);
In your case, you have to inject the request referrer handler in your sanitizer.
4. SOA
Service Oriented Architecture (SOA) applied to OOP is a great way to rationalize your code.
// Standard OOP
$car = new Car();
$buyer = new Person();
$store = new Store();
// Many solutions to buy a car!
$buyer->buy($car, $store);
// or
$store->sell($car, $buyer);
// or
// ...
In standard OOP, you are often confronted to duplicated code because of that. Where should I code this method? Where I can now where I (or someone else) already coded this method? This problem was so boring for me before! I was not able to rationalize my development.
// SOA
$car = new Car();
$buyer = new Person();
$store = new Store();
$saleHandler = new SaleHandler();
$saleHandler->sell($car, $buyer, $store);
In SOA, you have a "service class" (here SaleHandler) (basically implemented as a singleton) which handle the maniplation of "data classes" (here Car, Person and Store). There is no intelligence in your data classes (you often only have getters and setters on properties). This way, you know where is your code for sales!
In your case, it seems that your request referrer handlers and sanitizers are some kind of services, so it's ok.
Conclusion
In conclusion, you use intuitively some really good OOP practices. Now, you can apply them and know why!
However, I would highly advice you to try a framework like Symfony2. It will provide you with a strong base for you PHP development and a really nice dependency injection component allowing you to define all the dependencies of your classes in configuration files to have a real dynamical code. It will also help you to do SOA with its services.
Using a framework is a good thing to power up your developments and for your professional life (a developer knowing a framework is a lot more sought after). As PHP frameworks are mainly opensource, you can also participate and give you a nice visibility for recruiters.
If you want to use the same object of RequestReferrerHandler class to ClassA and ClassB, then your strategy is correct. Just need to use the object of RequestReferrerHandler class to instantiate ClassA and ClassB. Then you can access the particular methods. i.e. ClassA.queryArguments() or ClassB.queryVars()
If you want to create seperate object of RequestReferrerHandler class for ClassA and ClassB, you can extend RequestReferrerHandler class to ClassA and ClassB without defining the constructor. So, when you create object of ClassA, it automatically inherit the constructor method of RequestReferrerHandler class and you can access the property and method by parent: keyword. For example:
class ClassA extends RequestReferrerHandler
{
public function santizeAuthor()
{
$author = parent::hasAuthorReferrerValue(); // access the base class method
if ($author) {
$author = array_values($author);
$author = ['author' => (int)htmlspecialchars($author[0])]; //Will output ['author' => 1]
}
return $author; //Returns null or the array ['author' => 1]
}
public function santizeDate()
{
$date = parent::hasDateReferrerValue();
if ($date) {
// #TODO Still to work out
}
return $date;
}
//etc
public function queryArguments() // Will be used in the controller class ClassC
{
$queryArgs = null;
if ($this->santizeAuthor()) {
$queryArgs = $this->santizeAuthor();
} elseif ($this->santizeDate) {
$queryArgs = $this->santizeDate();
} // etc
return $queryArgs; //Will return null if all 4 conditions fail or return the value from the one that returns true
}
}
You can do as same as for ClassB. Now you can create object for ClassA and ClassB assigning the constructor’s argument of their base class in Class C and use the return value of ClassA.queryArguments() or ClassB.queryVars() from their object.
This problem can be simplified if you concentrated in the type of data instead of the meaning of the objects i were working in a api that is able to manipulate and get information from the Wordpress structure and i know that they already did a very nice job simplifying their data structure. so you only have to think in therms of post, post meta, and taxonomies (index) and not in meanings like (publication, tax, image, library, post, product).
Even in woo commerce you can see it that is very simple.
You would see that; a product is a type of post, that the data of the product is in post meta. a image is a type of post and the details of that image is in post_meta, that a news is a post and the details of this news s in post meta.
One structure different meaning. So for access everything or store data for everything you only need this elements. if you have access to this elements in a restful way the implementation is very easy to.
I hope this help you some how is just my point of view you can think otherwise. but i thought was important to share his with you.
I'm having trouble unit testing a class that has a method called within the constructor. I don't understand how to mock this. Perhaps I should use the 'setUp' method of phpUnit?
I'm using the Mockery library. Is there is a better tool than this?
class ToTest
{
function __construct() {
$this->methodToMock(); // need to mock that for future tests
}
// my methods class
}
Any suggestions would be appreciated.
If you class is difficult to instantiate to test, that is a code smell that your class is doing too much or doing work in the constructor.
http://misko.hevery.com/code-reviewers-guide/
Flaw #1: Constructor does Real Work
Warning Signs
new keyword in a constructor or at field declaration
Static method calls in a constructor or at field declaration
Anything more than field assignment in constructors
Object not fully initialized after the constructor finishes (watch
out for initialize methods)
Control flow (conditional or looping logic) in a constructor
Code does complex object graph construction inside a constructor
rather than using a factory or builder
Adding or using an initialization block
Whatever your methodToMock function does in your constructor needs to be rethought. As mentioned in the other answers, you probably want to use dependency injection to pass in things that your class is doing.
Rethink what your class is actually doing and refactor so that it is easier to test. This also has the benefit of making your class easier to reuse and modify later on.
The problem here is that the method can not be mocked as the object is not yet instantiated.
sectus answer is valid but maybe not very flexible, as it can be difficult to change the behavior of the mocked method on different tests.
You can create another class that does the same as the method you want to mock, and have an instance of that class passed as a constructor argument. That way you can pass a mock class on your test. Usually the problem you're having is a smell of a class doing too many things.
To test this class, you would mock the internal object (methodToMock) and then use Dependency Injection to pass the mocked service instead of the real one.
Class:
class ToTest{
private $svc;
// Constructor Injection, pass the Service object here
public function __construct($Service = NULL)
{
if(! is_null($Service) )
{
if($Service instanceof YourService)
{
$this->SetService($Service);
}
}
}
function SetService(YourService $Service)
{
$this->svc = $Service
}
function DoSomething($request) {
$svc = $this->svc;
$result = $svc->getResult($request); // Get Result from Real Service
return $result;
}
function DoSomethingElse($Input) {
// do stuff
return $Input;
}
}
Test:
class ServiceTest extends PHPUnit_Framework_TestCase
{
// Simple test for DoSomethingElse to work Properly
// Could also use dataProvider to send different returnValues, and then check with Asserts.
public function testDoSomethingElse()
{
$TestClass = new YourService();
$this->assertEquals(1, $TestClass->DoSomethingElse(1));
$this->assertEquals(2, $TestClass->DoSomethingElse(2));
}
public function testDoSomething()
{
// Create a mock for the YourService class,
// only mock the DoSomething() method. Calling DoSomethingElse() will not be processed
$MockService = $this->getMock('YourService', array('DoSomething'));
// Set up the expectation for the DoSomething() method
$MockService->expects($this->any())
->method('getResult')
->will($this->returnValue('One'));
// Create Test Object - Pass our Mock as the service
$TestClass = new ToTest($MockService);
// Or
// $TestClass = new ToTest();
// $TestClass->SetService($MockService);
// Test DoSomething
$RequestString = 'Some String since we did not specify it to the Mock'; // Could be checked with the Mock functions
$this->assertEquals('One', $TestClass->DoSomething($RequestString));
}
}
I was also wondering this which is how I found your question. In the end I decided to do something a little bit dirty... use reflection.
Here's the method I want to test:
/**
* ArrayPool constructor.
* #param array $tasks Things that might be tasks
*/
public function __construct(array $tasks)
{
foreach ($tasks as $name => $parameters) {
if ($parameters instanceof TaskInterface) {
$this->addTask($parameters);
continue;
}
if ($parameters instanceof DescriptionInterface) {
$this->addTask(new Task($parameters));
continue;
}
$this->addPotentialTask($name, $parameters);
}
}
For the purposes of this test, I don't want to actually run ->addTask or ->addPotentialTask, only know that they would be called.
Here's the test:
/**
* #test
* #covers ::__construct
* #uses \Foundry\Masonry\Core\Task::__construct
*/
public function testConstruct()
{
$task = $this->getMockForAbstractClass(TaskInterface::class);
$description = $this->getMockForAbstractClass(DescriptionInterface::class);
$namedTask = 'someTask';
$parameters = [];
$arrayPool =
$this
->getMockBuilder(ArrayPool::class)
->disableOriginalConstructor()
->setMethods(['addTask', 'addPotentialTask'])
->getMock();
$arrayPool
->expects($this->at(0))
->method('addTask')
->with($task);
$arrayPool
->expects($this->at(1))
->method('addTask')
->with($this->isInstanceOf(TaskInterface::class));
$arrayPool
->expects($this->at(2))
->method('addPotentialTask')
->with($namedTask, $parameters);
$construct = $this->getObjectMethod($arrayPool, '__construct');
$construct([
0=>$task,
1=>$description,
$namedTask => $parameters
]);
}
The magic happens in getObjectMethod which takes an object and returns a callable closure that will invoke the method on an instance of the object:
/**
* Gets returns a proxy for any method of an object, regardless of scope
* #param object $object Any object
* #param string $methodName The name of the method you want to proxy
* #return \Closure
*/
protected function getObjectMethod($object, $methodName)
{
if (!is_object($object)) {
throw new \InvalidArgumentException('Can not get method of non object');
}
$reflectionMethod = new \ReflectionMethod($object, $methodName);
$reflectionMethod->setAccessible(true);
return function () use ($object, $reflectionMethod) {
return $reflectionMethod->invokeArgs($object, func_get_args());
};
}
And I know the loop and the conditions all function correctly without going off into code I don't want to enter here.
TL;DR:
Disble __construct
Set up mocks
Use reflection to call __construct after the object was instantiated
Try not to lose any sleep over it
Just extend this class and override your method if it public or protected.
class ToTest
{
function __construct(){
$this->methodToMock(); // need to mock that for future tests
}
// my methods class
public function methodToMock(){}
}
class ToTestTest{
/**
* #test
* it should do something
*/
public function it_should_do_something(){
$ToTest = \Mockery::mock('ToTest')
->shouldDeferMissing()
->shouldReceive("methodToMock")
->andReturn("someStub")
->getMock();
$this->assertEquals($expectation, $ToTest->methodToMock());
}
}
i have class "User_registration" and in this class i need use many class: "Location", "Links", "Mail", "Module".
i create include all class in file:
include 'class/location.php';
include 'class/links.php';
include 'class/mail.php';
include 'class/modules.php';
Now create "User_registration" class.
<?php
class User_registration{
public function insert_to_db($name, $country_code, $home_page)
{
//work with data
return id;
}
public function show_info($id)
{
//work with data
}
}
$reg_u = new User_registration;
$res = $reg_u->insert_to_db($name, $country_code, $home_page);
if($res){
$reg_u->show_info($res);
}
?>
I need in method "insert_to_db" run class: "Location", "Links", "Mail" methods
and in "show_info" run some methods of "Location", "Links", "Module" class.
How? How in one class run another class (no't one)
Thanks for help ;)
There are a few ways to do this. If you have only a couple objects that another class needs to utilize, use dependency injection; Pass each object as an argument into a class's constructor and store those objects as a class property.
If only a single method needs the object, pass the object as an argument of the method. I discourage this approach though, because I feel it hinders expandability/code-cleanliness in the long run.
If you have many objects that are needed in several classes, I recommend a registry that you inject into a class's constructor. The registry is a singleton (it holds a single instance of each object you need to share). In the class that needs to utilize a shared object, you might call $this->registry->get('Some_Shared_Object')->doSomething().
Dependency Injection (at the constructor)
class Foo {
protected $dependency1;
protected $dependency2;
protected $dependency3;
public function __construct($dependency1, $dependency2, $dependency3) {
$this->dependency1 = $dependency1;
$this->dependency2 = $dependency2;
$this->dependency3 = $dependency3;
}
public function foo() {
$this->dependency1->doSomething();
}
}
$foo = new Foo($dependency1, $dependency2, $dependency3);
$foo->foo();
Dependency Injection (at the method, not recommended)
class Foo {
public function foo($dependency1) {
$dependency1->doSomething();
}
}
$foo = new Foo();
$foo->foo($dependency1);
Dependency Injection using a Registry
class Registry {
var $data = array();
function __get($key) {
return $this->get($key);
}
function __set($key, $value) {
$this->set($key, $value);
}
/**
* Retrieve a resource from the registry.
*
* #param string
* #return mixed|null
*/
function get($key) {
return isset($this->data[$key]) ? $this->data[$key] : NULL;
}
/**
* Store a resource in the registry.
*
* #param string
* #param mixed
*/
function set($key, &$value) {
$this->data[$key] = $value;
}
/**
* Check if a resource exists in the registry.
*
* #param string
* #return boolean
*/
function has($key) {
return isset($this->data[$key]);
}
}
class Foo {
protected $registry;
public function __construct($registry) {
$this->registry = $registry;
}
public function foo() {
$this->registry->dependency1->doSomething();
}
}
$dependency1 = new Dependency1();
$registry = new Registry();
$registry->set('dependency1', $dependency1);
$foo = new Foo($registry);
$foo->foo();
As good practice I always use an include_once/require_once when I call a class from a class. That way I know no matter where a use a class its references are taken care of and don't over lap.
Well initialize an instance of each and call your methods from there. Don't be afraid of static references.