Bad practice to expose private/protected methods during runtime - php

Okay I don't know if it's bad design but I feel a bit bad about doing the following:
abstract class A
{
abstract public function getCallable();
}
class B extends A
{
public function getCallable()
{
return array($this, 'doSomething');
}
protected function doSomething($param1, $param2, $param3)
{
// Do stuff here
}
}
The reason why B::doSomething is protected is that I don't like to expose this method because it should only be called from somewhere else in the code where I do a call_user_func() for the return value of B::getCallable.
You should be free to organize yourself in the subclasses of A. Without exposing anything to the outside. So the "API" won't change to the view outside of the subclasses of A.
So you should not be able to do something like:
$b = new B();
$b->doSomething($param1, $param2, $param3);
the only way to get B::doSomething executed should be over:
$b = new B();
call_user_func($b->getCallable());
So I'm thinking about how I could achieve that. One way I could think of is create a ReflectionMethod object from B::getCallable()'s return value and set it to accessable if the method is not public.
I don't like this solution it would work fine but is not that elegant:
class B extends A
{
public function getCallable()
{
return function($param1, $param2, $param3)
{
$this->doSomething($param1, $param2, $param3);
};
}
protected function doSomething($param1, $param2, $param3)
{
// Do stuff here
}
}
Any good suggestions or other ideas how to work around this?

Here is a more clear explanation of what i'm trying to articulate. In the below example you see that I come to realize there is no danger in exposing the doSomething function publicly, so I change it's scope instead of creating a clever intermediary function.
class One {
protected function doSomething( )
{
return 'hi';
}
}
class Two extends One {
public function doSomething( )
{
return parent::doSomething();
}
}
$Class = new Two();
echo $Class->doSomething();
Less is more, so KISS your code ;)

Related

Mock an object created in the middle of a method

I know that creating an instance of a Class in the middle of a method it's a bad practice since it makes code hard to test.
But I can't refactor the code, so I need to find a way to mock an Object created with new in the middle of a method under test.
Used Frameworks: PHPUnit, Mockery, WP_Mock
Example: Here I need to mock the get_second_string() method from the instance of the class ExternalClass
Class MyClass {
function methodUnderTest($string) {
$objToMock = new ExternalClass();
$second_string = $objToMock->get_second_string();
$final_string = $string . $second_string;
return $final_string;
}
}
Class TestMyClass extends PHPUnit_Framework_TestCase {
public function setUp() {
}
public function tearDown() {
}
public function test_methodUnderTest() {
$externalObject = $this->getMockBuilder('ExternalClass')
->setMethods(['get_second_string'])
->getMock;
$externalObject->expects($this->once())
->method('get_second_string')
->willReturn(' two');
$testObj = new MyClass();
$this->assertEquals('one two', $testObj->methodUnderTest('one');
}
}
If you really have no opportunity to refactor the code or do some appropriate integration testing, you might want to take a look at https://github.com/php-test-helpers/php-test-helpers#intercepting-object-creation and https://github.com/krakjoe/uopz/tree/PHP5
Still I think the code you make would profit a lot more from refactoring than monkey patching.
Besides, the refactoring does not need to be very heavy. You might do at least this:
class MyClass
{
private $externalsFactory;
public function __construct($externalsFactory){
$this->externalsFactory = $externalsFactory;
}
public function methodUnderTest($str){
$external = $this->externalsFactory->make();
$second_string = $external->get_second_string();
$finalString = $str.$second_string;
return $finalString;
}
}
class ExternalsFactory
{
public function make(){
return new ExternalClass();
}
}
class ExternalClass
{
public function get_second_string(){
return 'some real stuff may be even from database or whatever else it could be';
}
}
class MyClassTest extends PHPUnit_Framework_TestCase
{
private $factoryMock;
private $myClass;
public function setUp(){
$this->factoryMock = $this->getMockBuilder('ExternalsFactory')
->getMock();
$this->myClass = new MyClass($this->factoryMock);
}
public function testMethodUnderTest(){
$extenalMock = $this->createMock('ExternalClass');
$extenalMock->method('get_second_string')
->willReturn('second');
$this->factoryMock->method('make')
->willReturn($extenalMock);
$this->assertSame('first-and-second', $this->myClass->methodUnderTest('first-and-'));
}
}
IMHO there is no way to do such a thing. You should pass the object as parameter to the method.
You can not mock entire object.
But with phpunit you can do something like this:
$f = $this->getMockBuilder(<your_class>)->disableOriginalConstructor()
->setMethods(array(
<mocked_method_1>, <mocked_method_2>
))->getMock();
This way, newly created object ommits constructor and you specify which method are going to behave normally and which you mock.
in testing you can specify what the method/s will return, like this:
$f->method(<mocked_method_1>)->willReturn(<dummy_data>);
using this, you will not test the mocked object in any way, but can test method which is creating the object..

Is it possible to cancel function override in parent class and use function from top level parent

class TopParent
{
protected function foo()
{
$this->bar();
}
private function bar()
{
echo 'Bar';
}
}
class MidParent extends TopParent
{
protected function foo()
{
$this->midMethod();
parent::foo();
}
public function midMethod()
{
echo 'Mid';
}
public function generalMethod()
{
echo 'General';
}
}
Now the question is if I have a class, that extends MidParent because I need to call
class Target extends MidParent
{
//How to override this method to return TopParent::foo(); ?
protected function foo()
{
}
}
So I need to do this:
$mid = new MidParent();
$mid->foo(); // MidBar
$taget = new Target();
$target->generalMethod(); // General
$target->foo(); // Bar
UPDATE
Top parent is ActiveRecord class, mid is my model object. I want to use model in yii ConsoleApplication. I use 'user' module in this model, and console app doesn't support this module. So I need to override method afterFind, where user module is called. So the Target class is the class that overrides some methods from model which uses some modules that console application doesn't support.
Try this (http://php.net/manual/en/language.oop5.final.php - not allow to overriding in the childrens):
final protected function foo()
{
$this->midMethod();
parent::foo();
}
in class MidParent and the class Target can't overrides this method.
Directly - you can't. This is how OOP works.
You can do it by a little redesign, e.g. in MidParent add method:
protected function parentFoo()
{
parent::foo();
}
and in Target:
public function foo()
{
$this->parentFoo();
}
But, again, this is only a workaround to solve your question and not a solution.
Actually, you can do this like this way with Reflection::getParentClass():
class Foo
{
public function test($x, $y)
{
echo(sprintf('I am test of Foo with %s, %s'.PHP_EOL, $x, $y));
}
}
class Bar extends Foo
{
public function test()
{
echo('I am test of Bar'.PHP_EOL);
parent::test();
}
}
class Baz extends Bar
{
public function test()
{
$class = new ReflectionClass(get_class($this));
return call_user_func_array(
[$class->getParentClass()->getParentClass()->getName(), 'test'],
func_get_args()
);
}
}
$obj = new Baz();
$obj->test('bee', 'feo'); //I am test of Foo with bee, feo
-but this is an architecture smell in any case. If you need something like this, that should tell you: you're doing something wrong. I don't want to recommend anyone to use this way, but since it's possible - here it is.
#AnatoliyGusarov, your question is interesting and in a sense you can achieve what you desire using yii and php advances features like Traits and Traits in Yii.
Given that it depends on what version of php you are using.However in yii you can achieve this by behaviors and check this SOQ.
In a nutshell you have to use language advanced features or YII framework features to come around this kind of issues,but that boils down to actual requirements

Basic OOP architecture about an application with a single point of entry - PHP?

My application has a single point of entry let's call it index.php.
In index.php it instantiates a class like below;
final class Griff {
public $a, $b, $c, $d, $e;
public function __construct() {
spl_autoload_register(array($this, 'autoload',));
$this->a = 'a';
// blah blah blah
new RouterGriff($this);
}
private function autoload($name) {
// autoload function
}
}
new Griff();
You will notice that RouterGriff is instantiated inside Griff::__construct(), RouterGriff looks like below:
final class RouterGriff {
private $griff;
public function __construct(Griff $griff) {
$this->griff = $griff;
$this->griff->b = 'b';
$this->griff->c = 'c';
}
}
My question is as follows; you will notice I am setting variables for properties that are stored in Griff from RouterGriff as I want a registry kind of structure to my application but do not want to use a singleton.
Would it be better if I just had the properties set in RouterGriff instead of Griff? Or is passing Griff around to every class a valid way of doing things, considering my application could go 10 classes deep before it outputs anything?
I hope I made sense and thank you
EDIT
By the other way I ment doing it this way,
final class Griff {
public $a;
public function __construct() {
spl_autoload_register(array($this, 'autoload',));
$this->a = 'a';
// blah blah blah
new RouterGriff();
}
private function autoload($name) {
// autoload function
}
}
new Griff();
final class RouterGriff {
public $b, $c;
public function __construct() {
$this->b = 'b';
$this->c = 'c';
}
}
The answer to you question you are looking for is named "dependency injection" or "dependency injection container". This is a wide topic filling books. If you are interessted in this topic, I can suggest you: Clean Code: A Handbook of Agile Software Craftsmanship (Robert C. Martin)
If you're talking about global application settings (Which I think you are), just use constants.
They're global, but cannot be changed, and they are easy to write and use.
I dont think youre wrong about injecting Griff into everything that is fine. As far as what classes get which propertied i think the depends on the nature of the properties. For example i would have included a GriffRequest in this example which is where you would access/store the parsed params the router supplied for example:
final class Griff {
protected $_request,
$_response,
$_router;
public function __construct() {
spl_autoload_register(array($this, 'autoload',));
$this->_request = new GriffRequest($this);
$this->_response = new GriffResponse($this);
$this->_router = new RouterGriff($this);
}
private function autoload($name) {
// autoload function
}
public function getRequest() { return $this->_request; }
public function getResponse() { return $this->_response; }
public function getRouter() { return $this->_router; }
}
final class RouterGriff {
protected $_application;
public function __construct(Griff $application) {
$this->_application = $application;
}
public function route() {
// do stuff with request
// assign params
$request = $application->getRequest();
$request->a = 'value';
$request->b = 'value2';
}
}
If you make Griff store a ref to all the important submodules of the app and then inject Griff into all those modules then you can get access to everything you might need by going through Griff at some point. You can always make convenience getters if you feel the chain is to long.

PHP - Argument with static variable in called function

I got a doubt while doing this:
class Logger {
public static $log_INFO = 'INFO';
public static $log_ERROR = 'ERROR';
public function log($logLevel, $param2, $param3) {
// Write log to some file
}
}
class Midea {
public function fn1 {
$logger = new Logger();
$logger->log(Logger::$log_INFO, 'some', 'some2');
}
}
Now my question is: Is there any way to make the log function in Logger class to accept only the static variables (any static variable) of Logger class? It should not accept any other string or integers as arguments.
My answer was based on the fact that $logLevel contains the name of a static class property.
If you use it as the updated example Logger::$INFO, that will pass the value string(4) "INFO" and this will not work. it needs to pass the value string(8) "log_INFO"
Yes, by using reflection:
public function log($logLevel, $param2, $param3) {
$reflection_property = new ReflectionProperty(get_called_class(), $logLevel);
if($reflection_property->isStatic()) {
// rest of the code
}
}
IMO this kind of enforcement is unnecessary, it adds both complexity and overhead to the code. And the benefits are small.
Coding your necessity like this seams more appropriate to me:
public static function $log_levels = array('INFO', 'ERROR');
public function log($log_level, $param2, $param3) {
if(in_array($log_level, static::$log_levels)) {
// code
}
}
The structure above opens up a neat opportunity:
public static function $log_levels = array(
'INFO' => array('Logger', 'handleInfoLogs'),
'ERROR' => array('Logger', 'handleErrorLogs')
);
public function log($log_level, $param2, $param3) {
if(array_key_exists($log_level, static::$log_levels)) {
return(static::$log_levels[$log_level]($param2, $param3));
}
}
What you are asking for is akin to enums in the Java world. Check this question on SO, which has some information on how you can implement similar concepts in PHP.
More specifically, you could implement what you are asking for like this:
class Logger {
const INFO = 1;
const ERROR = 2;
};
You could then use it in code like:
Logger::INFO
It isn't perfect, but I believe it is as close as it gets in PHP. To make it bullet-proof, you would have to employ some reflection to check the arguments passed in. This answer on SO has more information on how you can go about implementing it.
It's quite cumbersome but you could do this:
abstract class LoggerStatus
{
public function __toString()
{
return $this->status;
}
}
class LoggerStatusInfo extends LoggerStatus
{
protected $status = 'INFO';
}
class LoggerStatusError extends LoggerStatus
{
protected $status = 'ERROR';
}
class Logger {
public static $log_INFO;
public static $log_ERROR;
public function __construct()
{
self::$log_INFO = new LoggerStatusInfo();
self::$log_ERROR = new LoggerStatusError();
}
public function log(LoggerStatus $logLevel, $param2, $param3) {
// Write log to some file
}
}
I've never attempted this myself but I don't see any reason it wouldn't work. Personally, I'd go for something simpler.

Pull variables from separate function

If I have two public functions and I want to pull a variable from one inside of another, what is the best way to accomplish this? I know about 'global' but this method seems like it could cause me problems down the road.
Imagine I have something like this:
class myCMS {
public function process_apples() {
$a = $_POST['$apples'];
}
public function display_apples() {
echo $a;
}
}
How would I go about using display_apples() to report $a from process_apples()? Im new to PHP, so feel free to let me know if I am violating some best practicing for organizing my code.
If you have two methods in a class, and want a variable to shared between them, you should use a class property -- which is kind of a variable that's inside a class.
Your class would then look like this :
class myCMS {
protected $a; // declare the property (won't be visible from outside the class)
public function process_apples() {
$this->a = $_POST['$apples'];
}
public function display_apples() {
echo $this->a;
}
}
And a couple of notes :
You need to use $this->property_name to access a property
Not related, but you should generally no use $_POST from inside your class : it makes your class dependent on external global variables -- of course, up to you to determine whether it's a problem or not.
You would have:
class myCMS {
private $a;
public function process_apples() {
$this->a = $_POST['$apples'];
// process
}
public function display_apples() {
echo $this->a;
}
}
Since you're using a class, you could make a private class variable, like this:
class myCMS {
private $a;
public function process_apples() {
$this->a = $_POST['$apples'];
}
public function display_apples() {
echo $this->a;
}
}

Categories