Why can I call non-static function without declaring the class object? - php

I am using Symfony 1.0, and I have this MyClassInc.class.php in my project/lib folder
class MyClassInc {
public function validateFunction ($params) {
// my codes
}
static function testFunction ($params){
// my codes
}
}
Then, my action actions.class.php in my project/apps/myapps/modules/actions.
class inventoryCycleCountActions extends sfActions
{
public function validateOutstandingTransaction () {
$res0 = MyClassInc :: validateFunction($param); // It works
$res1 = MyClassInc :: testFunction($param); // It works
$myClass = new MyClassInc();
$res2 = $myClass->validateFunction($param); // still works
$res3 = $myClass->testFunction($param); // still works, da hell?
}
}
I tried to clear my cache folder to do re-test, but it seems that all of those work just fine.
Question:
So.. WHY? and which one should I use? Does it have any effect with performance or anything?
Update 1:
class MyClassInc {
public function isProductValidated ($product){
return true;
}
public function validateFunction ($params) {
// IF, I call by using "$res0".. Throws error
//
$this->isProductInLoadPlans($product);
}
}
If I call validateFunction via $res0, it will throw this error:
sfException: Call to undefined method
inventoryCycleCountActions::isProductValidated.
And, if I call it via $res2, it works just fine.
Since, I am currently using $res0 and so I have to call that method like this instead.
MyClassInc :: isProductValidated ($product)

The only real difference between :: and -> is how $this is handled. With :: the function will have $this as it was defined in the caller's scope:
class A {
public function foo() {
A::bar();
A::foobar();
}
static private function bar() {
// $this here is the instance of A
}
static public function foobar() {
// Here you can have anything in $this (including NULL)
}
}
$a = new A;
$a->foo();
$a->foobar(); // $this == $a but bad style
A::foobar(); // $this == NULL
When you want to use an instance method, you should use -> because that would resolve instance methods properly (including inheritance). :: will always call the method of the specified class.
I believe there is effort made now to enforce calling static methods only as statically and dynamic methods only dynamically to avoid the confusion.

Related

PHP 8 migration Fatal error: Declaration must be compatible

We are moving forward to PHP 8 from PHP 7.4, we are facing Declaration must be compatible Fatal error in our code to custom parameter type, we need a proper solution with less code changes.
Kindly refer below code snippet
ERROR
Fatal error: Declaration of ClientReactieView::getUrlAddData(?ClientReactie $clientReactie = null) must be compatible with Overview\BaseView::getUrlAddData(?Storm\Model $model = null) in /var/www/html/system/tmp/class_client_reactie_view.php on line 102
abstract class file : abstract_class_base_view.php
<?php
namespace Overview;
use Storm;
abstract class BaseView {
public static function getUrlAddData(Storm\Model $model = null){
// ...
return $urlAddData;
}
}
Child class file : class_client_reactie_view.php
<?php
class ClientReactieView extends Overview\BaseView {
public static function getUrlAddData(ClientReactie $clientReactie = null){
// ...
return $urlAddData;
}
}
Custom parameter type class_client_reactie.php
class ClientReactie extends Storm\Model {
// ...
}
Our application is already developed & working fine with PHP 7.4, We require solution to resolve this fatal error with less code changes
The error is telling your code is illogical.
The definition of BaseView makes a promise: you can call getUrlAddData and pass any instance of Storm\Model, or null.
The definition of ClientReactieView says that it extends BaseView, so inherits that promise. But then it changes that promise: you can call getUrlAddData, but you're not allowed to pass anything that's not a ClientReactie.
This is "covariance of input", and has always been forbidden for non-static methods - if $foo instanceof BaseView is true, then $foo->getUrlAddData(...) would have to accept all values that the definition in BaseView allowed.
What's new in PHP 8 is that this is enforced for static methods as well, so that the same guarantee applies to "late static binding" calls such as this:
abstract class BaseView {
public static function getUrlAddData(Storm\Model $model = null){
// ...
return $urlAddData;
}
public static function doSomethingElse(Storm\Model $model = null){
$urlAddData = static::getUrlAddData($model);
// ...
}
}
class ClientReactieView extends Overview\BaseView {
public static function getUrlAddData(ClientReactie $clientReactie = null){
// ...
return $urlAddData;
}
}
ClientReactieView::doSomethingElse(new Storm\Model);
// ERROR! The call goes to BaseView::doSomethingElse, which accepts any Model
// But static::getUrlAddData resolves to ClientReactieView::getUrlAddData
// and that has an incompatible signature
So the correct fix is to honour the promise made by the parent class:
class ClientReactieView extends Overview\BaseView {
public static function getUrlAddData(Storm\Model $model = null){
if ( ! $model instanceof ClientReactie ) {
// Figure out what to do with such calls
}
// ...
return $urlAddData;
}
}

PHPUnit: How to mock an internally called function by the method under test

In the below example, I want to mock the calling of getBaseValue() inside the multipliedValue(). But I cannot figure it out.
class Sample
{
function multipliedValue()
{
$value = $this->getBaseValue();
return $value * 2;
}
function getBaseValue()
{
return 2;
}
}
I have used PHPUnit mocking, but it didn't work. So, I used the following code:
class SampleTest extends PHPUnit_Framework_TestCase
{
function testMultipliedValueIfBaseValueIsFalse()
{
$mockedObject = $this->getMockBuilder(Sample::class)
->setMethods(['multipliedValue', 'getBaseValue'])
->getMock();
$mockedObject->expects($this->any())
->method("getBaseValue")
->willReturn(false);
$result = $mockedObject->multipliedValue();
$this->assertFalse($result);
}
}
I tried to create a global function, but only force one of the method to return my desired value, the rest just go as they are. How should I approach this test?
The error I am currently getting is for the $this in the multipliedValue() method, which treats it as the stubbed object.
All the methods listed in the ->setMethods() will be stubbed and return null by default so if you only want to stub getBaseValue then do:
$mockedObject = $this->getMockBuilder(Sample::class)
->setMethods(['getBaseValue'])
->getMock();
$mockedObject->expects($this->any())
->method("getBaseValue")
->willReturn(false);
$result = $mockedObject->multipliedValue();
$this->assertFalse($result);

PHPUnit call to undefined method `Mock_x_::method()`

I'm trying to create my first phpunit test and find myself needing to stub a method on an IMailer interface.
interface IMailer
{
public function send($to, $from, $cc, $subject, $body);
public function sent();
}
$mailer = $this->getMockBuilder(
'IMailer',
array('send', 'sent'))->getMock();
$mailer->method('send')->willRreturn(0);
However, I keep getting
PHP Fatal error:
Call to undefined method Mock_Mailer_13fc0a04::method()
in ...Test.php on line 16
a var_dump($mailer); results in
class Mock_IMailer_4c3e02a7#215 (1) {
private $__phpunit_invocationMocker =>
NULL
}
Working with the expect($this->any()) gives a dito error - it seems that the mocked object does not have any mock functionality...
I'm running phpunit 3.7.28, and php 5.5.9, on an ubuntu box.
How come? How can I fix it?
The getMockBuilder function accepts only the className as parameter. The correct way to initialize your mock object methods would be to use setMethods function (see phpunit docs)
$mailer = $this->getMockBuilder('IMailer')
->setMethods(array('send', 'sent'))
->getMock();
Additionally you probably want to have some expects definition also when you use your mock object:
$mailer->expects($this->any())
->method('send')
->willReturn(0);
EDIT
The above holds true for newer phpunit versions. For phpunit 3.7.28 the mock object usage is a bit different (i.e. the expects seems to be mandatory and willReturn is not yet available). For 3.7.28 version you should modify the second part to:
$mailer->expects($this->any())
->method('send')
->will($this->returnValue(0));
I would recommend updating to later phpunit version as it seems to be somewhat difficult to find documentation to this much older releases.
An alternative solution, for anybody that is still using old versions of PHPUnit, but still wants to be able to call method() directly, is to override the default mock object class template.
Copy MockObject/Generator/mocked_class.tpl.dist, and name the copy mocked_class.tpl. Then, just add the code for the method() method to the template:
public function method()
{
$any = new PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount;
$expects = $this->expects($any);
$args = func_get_args();
return call_user_func_array(array($expects, 'method'), $args);
}
This will allow you to call $mock->method() directly. However, you need to still use ->will($this->returnValue(0)) instead of ->willReturn(0). To do that, you need to introduce a custom invocation builder and invocation mocker:
class My_MockObject_Builder_InvocationMocker
extends PHPUnit_Framework_MockObject_Builder_InvocationMocker {
public function willReturn( $value ) {
return $this->will( new PHPUnit_Framework_MockObject_Stub_Return( $value ) );
}
}
class My_MockObject_InvocationMocker
extends PHPUnit_Framework_MockObject_InvocationMocker {
public function expects( PHPUnit_Framework_MockObject_Matcher_Invocation $matcher ) {
return new My_MockObject_Builder_InvocationMocker($this, $matcher);
}
}
And update your template again, to use My_MockObject_InvocationMocker instead of PHPUnit_Framework_MockObject_InvocationMocker.
The full template would then look like this:
{prologue}{class_declaration}
{
protected static $staticInvocationMocker;
protected $invocationMocker;
{clone}{mocked_methods}
public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher)
{
return $this->__phpunit_getInvocationMocker()->expects($matcher);
}
public function method()
{
$any = new PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount;
$expects = $this->expects($any);
$args = func_get_args();
return call_user_func_array(array($expects, 'method'), $args );
}
public static function staticExpects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher)
{
return self::__phpunit_getStaticInvocationMocker()->expects($matcher);
}
public function __phpunit_getInvocationMocker()
{
if ($this->invocationMocker === NULL) {
$this->invocationMocker = new My_MockObject_InvocationMocker;
}
return $this->invocationMocker;
}
public static function __phpunit_getStaticInvocationMocker()
{
if (self::$staticInvocationMocker === NULL) {
self::$staticInvocationMocker = new My_MockObject_InvocationMocker;
}
return self::$staticInvocationMocker;
}
public function __phpunit_hasMatchers()
{
return self::__phpunit_getStaticInvocationMocker()->hasMatchers() ||
$this->__phpunit_getInvocationMocker()->hasMatchers();
}
public function __phpunit_verify()
{
self::__phpunit_getStaticInvocationMocker()->verify();
$this->__phpunit_getInvocationMocker()->verify();
}
public function __phpunit_cleanup()
{
self::$staticInvocationMocker = NULL;
$this->invocationMocker = NULL;
}
}{epilogue}

Pass the current class as parameter

I have a class like this:
// file /models/person.php
class Person
{
public function create_path()
{
self::log();
path_helper($this); //a global function in other php file
}
public function log()
{
echo "trying to create a path";
}
}
This is the way how Person is instanciated:
//file /tools/Builder.php
include('/models/Person.php');
class Builder
{
public function build()
{
$type = 'Person';
$temp = new $type();
$temp->create_path();
}
}
As you note in Person class, I am calling the object in question with $this reference. But this is not correct because an error is showed:
Message: Undefined variable: this
I suppose that $this reference point to other object or it is unable to work because the object is created from another script. Also, I tried to use self because there was not problem calling methods with that, but as parameter I get:
Message: Use of undefined constant self - assumed 'self'
So, can you guide me to the right direction?
I tested your code out for myself, with a few minor changes. It appears to work properly.
Changed self::log() to $this->log()
Added global function path_helper (I have no idea what this does)
PHP
function path_helper(Person $object)
{
var_dump($object);
}
class Person
{
public function create_path()
{
$this->log();
path_helper($this); //a global function in other php file
}
public function log()
{
echo "trying to create a path";
}
}
class Builder
{
public function build()
{
$type = 'Person';
$temp = new $type();
$temp->create_path();
}
}
$Build = new Builder();
$Build->build();
Result
trying to create a path
object(Person)[2]
Your code is correct and your going in the right direction.
You should call the log method like this:
$this->log();
because using self:: is reserved for static methods.
Also, try calling the path_helper function like this:
path_helper(self);
Hope I could help you. Couldn't test it, but it should work.

Calling a Class function without using $this->function_name() -- PHP --

So I have this class:
class A{
public function do_a(){ return 'a_done';};
public function do_b(){ return 'b_done';};
}
So I require the php file and create an instance of the class:
require_once("A_class.php");
$System = new A();
require_once("user_calls.php"); //here I import the user file with the function calls.
user_calls.php contents:
echo 'this was the result of '.$System->do_a();
echo 'this was the result of '.$System->do_b();
So, that does work, but I don't want the user to have to use $System->do_a();, but only do_a();.
Any solutions?
EDIT: I also want to limit the functions the user could call in the user_calls.php file, to basic native php functions and those in class A.
DISCLAIMER: While this code works, and does what you requested, that doesn't mean that I advocate coding like this. It's very hard to follow for other developers (and maybe even you in the future...), and it also makes use of eval(), which is almost always A Bad Thing(tm). That said, here you go:
<?php
class A {
public function do_a() {
return __METHOD__;
}
public function do_b() {
return __METHOD__;
}
}
$aRef = new ReflectionClass('A');
$aPublicMethods = $aRef->getMethods(ReflectionMethod::IS_PUBLIC);
foreach ($aPublicMethods as $method) {
$php = <<<PHP
function {$method->name}() {
global \$System;
return \$System->{$method->name}();
}
PHP;
eval($php);
}
$System = new A();
echo 'this was the result of ' . do_a();
echo 'this was the result of ' . do_b();
Please also note that if your methods use arguments, things get even more hairy. Also, if you name any of your methods the same as a function in the global namespace (ex. substr()), this will attempt to redefine them, and you'll probably get a Fatal Error.
Methods of a class are either instance methods (they act on a particular instance of a class defined by $this) or they are class methods (They aren't tied to any one particular instance of a class, but provide services that fall within the remit of the class.
An instance method is defined as follows:
public function foo()
{
}
whereas a class method is defined with the STATIC keyword.
static public function bar()
{
}
In the instance method you can use $this to get access to the state of the instance on which the method was called. This is not available in the class method because it's not tied to any one instance. It can access other members of the class (provided they're not tied to an instance) with the self keyword though.
Instance methods are called as follows:
$a = new ObjType ()
$output = $a -> foo ();
Class methods are called as follows:
$output = ObjType::bar ();
No matter which approach you use you either have to provide an instance (for instance methods) or a class (for class methods) to call the method. Calling just foo() or bar() will not work.
You'll have to use a closure. Note that it's calling directly from the class definition, not the object:
class test {
function method() {
echo 'method was called';
}
}
$method = function(){call_user_func('test::method');};
$method();
$method();
$method();
//output:
//method was calledmethod was calledmethod was called
To call the method from the object, rather than the class, you'll have to pass the object into the closure:
class test {
var $count = 0;
function method() {
$this->count++;
echo $this->count . "|<br />";
}
}
$obj = new test;
$obj2 = new test;
$method = function($object){call_user_func(array($object, 'method'));};
$method($obj);
$method($obj);
$method($obj);
$method($obj2);
//output:
//1|
//2|
//3|
//1|
But that's not any prettier or simpler, is it?
If you don't want to clutter up your page, just name the object something short:
$pco = new page_controller_object_with_a_long_name_that_is_annoying;
$pco->do_a();
$pco->do_b();
//etc.
Moving it outside the class as suggested by #LucM sounds the easiest way.

Categories