Following the advice in this posting: php class as a plugin in wordpress
I've created a helper class for use with other plugins. In the class file, I have a declaration for activating the class, like:
function test_init() {
$test = new Test();
} // End of test_init()
I'm able to access the functions in this class by doing something like:
Test::my_function();
However, I'm having issues referring to functions within this class from each other. For example:
function my_function() {
Test::other_func();
}
In a case like this, I get the error message: "Function name must be a string"
I've tried $this->other_func, which returns the error: "there is not function "other_func" in the Class_Using_The_Test_Class.
I've tried self::other_func, which return the error: "Function name must be a string"
I tried using call_user_func() and I get: "call_user_func() expects parameter 1 to be a valid callback"
How do I call another function within this class?
You don't actually need to activate the class. I'll give an example.
Let's say this code lives in helper-class.php:
<?php
class Helper_Class {
// Note: those are double underscores before the word 'construct'.
function __construct() {
// initialize/call things here.
$this->init(); // this is how you call class functions.
}
function init() {
// do some monkey-business
return;
}
// we'll call this function from our other class.
function some_function() {
// do the fancy whizbang.
}
}
?>
Now, over in your other class file you could have something like this:
<?php
// give ourselves access to the helper class.
require_once 'helper-class.php';
class Main_Class {
// Note: those are double underscores before the word 'construct'.
function __construct() {
$this->init();
}
function init() {
// classes can't be used until an object of that class is created.
$helper_class_object = new Helper_Class;
// now I can call functions in my helper class.
$helper_class_object->some_function();
return;
}
}
?>
I hope this sheds a bit of light on your situation. Just ask if you'd like further clarification. :)
Related
With the following class
declare(strict_types=1);
abstract class IntValueObject
{
public function __construct(protected int $value)
{
}
}
and the test
declare(strict_types=1);
final class IntValueObjectTest extends TestCase
{
public function testWithNotValidValue(): void
{
$value = '1';
$this->expectException(\TypeError::class);
$this->getMockForAbstractClass(IntValueObject::class, [$value]);
}
}
return
Api\Tests\Shared\Domain\ValueObject\IntValueObjectTest::testWithNotValidValue
Failed asserting that exception of type "TypeError" is thrown.
If I change $value from '1' to 'foo' if it passes the test.
We use PHP 8, and in production, if the value '1' is passed it would give TypeError, why doesn't this happen in the test?
Thanks in advance.
ORIGIN OF THE "PROBLEM"
https://bugs.php.net/bug.php?id=81258
POSSIBLE SOLUTION
declare(strict_types=1);
final class IntValueObjectTest extends TestCase
{
public function testWithNotValidValue(): void
{
$value = '1';
$this->expectException(\TypeError::class);
new class($value) extends IntValueObject {};
}
}
One explanation I can imagine is that during the test, IntValueObject::__construct('1') is called from code that is not using declare(strict_types=1); and therefore the string '1' is being coerced to integer 1. No TypeError is thrown in that case (but it would for string 'foo' - as you describe the behaviour in your question).
The Cause
The cause is using a generated mock:
<?php declare (strict_types = 1);
...
$this->expectException(\TypeError::class);
$this->getMockForAbstractClass(IntValueObject::class, [$value]);
...
To not have a TypeError in this situation is likely unexpected to you as you have scalar strict types but still see the type-coercion of the string '1' to integer 1 for the constructors' first parameter.
The Mismatch
However the TypeError is only thrown when the code calling IntValueObject::__construct(int $value) has declare(strict_types=1).
While the test-code has declare(strict_types=1) it must not be that code where the constructor method is called - as no TypeError is thrown.
For Real
Behind the scenes $this->getMockForAbstractClass(...); uses an Instantiator from the Doctrine project which is making use of PHP reflection (meta-programming). As those methods are all internal code, declare(strict_types=1) is not effective and there is no TypeError anymore.
Compare with the following code-example:
<?php declare(strict_types=1);
class Foo {
public function __construct(int $integer) {
$this->integer = $integer;
}
}
try {
$foo = new Foo('1');
} catch (TypeError $e) {
} finally {
assert(isset($e), 'TypeError was thrown');
assert(!isset($foo), '$foo is unset');
}
$foo = (new ReflectionClass(Foo::class))->newInstance('1');
var_dump($foo);
When executed with assertions enabled, the output is the following:
object(Foo)#3 (1) {
["integer"]=>
int(1)
}
Within the try-block, the TypeError is thrown with new as you expect it.
But afterwards when instantiating with PHP reflection it is not.
(see as well https://3v4l.org/aZTJl)
The Remedy
Add a class to your test-suite that is really mocking the abstract base class and place it next to the test of it so you can easily use it:
<?hpp declare(strict_types=1);
class IntValueObjectMock extends IntValueObject
{...}
Then use IntValueObjectMock in your test:
$value = '1';
$this->expectException(\TypeError::class);
new IntValueObjectMock($value);
Alternatively use anonymous class when extending it is straight forward:
$value = '1';
$this->expectException(\TypeError::class);
new class($value) extends IntValueObject {};
Or apply type-checks on the constructor method your own, either with PHP reflection or run static code-analysis which has the benefit that it can detect such issues already without instantiating - so no additional test-code is involved.
I am working on a fork of the SilverStripe AMP module (https://github.com/thezenmonkey/silverstripe-amp) and have to prevent anyone from navigating to the amp.html version of a SilverStripe page if the amp.html link has not been generated into the head of the page.
A little additional info: We have added 2 fields that are meant to appear on the AMP versions of every page: AmpImage and AmpContent (a rich text editor). If either of these is empty, I have the code setup to NOT generate an AMP page for the SilverStripe page in question. This would seem to be enough but an additional requirement has been added, which is the redirect functionality mentioned about so no one can actually navigate to the amp.html page.
I was thinking of doing a redirect with the AmpSiteTreeExtension file but it does not appear to allow for redirects, then I thought of having a function in Page.php that would check if the url contains amp.html, then referencing it with AmpSiteTreeExtension, but every time I try, I get an error saying the function does not exist on "Page" or it's not public.
Is there a good way to handle this kind of situation? Is it best to do it with Page.php or using some other method?
Here are the files that I am working with:
AmpSiteTreeExtension
public function MetaTags(&$tags)
{
if ($this->owner->AmpContent != "" && $this->owner->AmpImageID != "") {
if ($this->owner->class != "HomePage") {
$ampLink = $this->owner->AbsoluteLink() . "amp.html";
} else {
$ampLink = $this->owner->AbsoluteLink() . "home/" . "amp.html";
}
$tags .= "<link rel='amphtml' href='$ampLink' /> \n";
}
//add a redirect here? Referencing a function from Page.php like so: $this->owner->functionName() causes the error mentioned above
}
}
<?php
AmpController
class AmpController extends Extension
{
private static $allowed_actions = array('amp');
private static $url_handlers = array(
'amp.html' => 'amp'
);
public function amp()
{
Requirements::clear();
$class = Controller::curr()->ClassName;
$page = $this->owner->renderWith(array("$class"."_amp", "Amp"));
return $this->AmplfyHTML($page);
}
public function AmplfyHTML($content)
{
if (!$content) {
return false;
}
$content = preg_replace('/style=\\"[^\\"]*\\"/', '', $content);
$content = str_replace("<img", "<amp-img", $content);
return $content;
}
}
From what I can tell, you're trying to redirect in the MetaTags() method of the SiteTree extension... and from what I can tell, that MetaTags() method it's probably used in some Silverstripe templates like this: $MetaTags
...and there's no way you can apply redirects this way.
You should do all this redirect stuff in a controller class, and from your example that controller it's probably the AmpController class which is probalby extending the functionality of the Page_Controller class.
Now I'll assume that AmpController it's an extension of Page_Controller, so I would do it like this:
class Page_Controller extends ContentController {
public function init() {
parent::init();
// you might have some other stuff here
// make sure this is the last line in this method
$this->extend('updateInit');
}
public function yourRedirectMethod() {
// do your redirect thing here
}
}
Key here is the following:
I extend the init() method in the controller - this will allow me to
extend the page controller's init() functionality using the
updateInit() method in the extension class (AmpController in this
case).
Instead of adding the method that's doing the redirect, to the Page
class, I added it to the Page_Controller class (the
yourRedirectMethod() method).
Now here comes the AmpController class, where I implement the updateInit() method:
class AmpController extends Extension {
private static $allowed_actions = array('amp');
private static $url_handlers = array(
'amp.html' => 'amp'
);
public function amp()
{
Requirements::clear();
$class = Controller::curr()->ClassName;
$page = $this->owner->renderWith(array("$class"."_amp", "Amp"));
return $this->AmplfyHTML($page);
}
public function AmplfyHTML($content)
{
if (!$content) {
return false;
}
$content = preg_replace('/style=\\"[^\\"]*\\"/', '', $content);
$content = str_replace("<img", "<amp-img", $content);
return $content;
}
public function updateInit() {
$should_redirect = true; // of course you add your own condition here to decide wether to redirect or not
if ($should_redirect) {
$this->owner->yourRedirectFunction();
}
}
}
The only thing here, is that you'll need to update the $should_redirect variable above (I've set it to true by default for this example - but here's where you decide if you should redirect or not)... and yes, you can reference here in the AmpController class stuff from the Page class I think, like this for exmaple: $this->owner->Title
I am currently migrating an existent application to Symfony2 that has about 100 controllers with approximately 8 actions in each controller. All the current Actions are named as follow:
public function index(){}
However the default naming convention for Symfony is indexAction().
Is it possible to keep all my current actions and tell Symfony to use as it is without the "Action" word after the method name?
thank you.
Yes, this is possible. You should be able to define routes as normal, but you need to change the way the kernel finds the controller. The best way to do this is to replace/decorate/extends the service 'controller_name_converter'. This is a private service and is injected into the 'controller_resolver' service.
The source code of the class you want to replace is at 'Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser'.
Basically, the code runs like this. The 'bundle:controller:action' you specified when creating the route is saved in the cache. When a route is matched, that string is given back to the kernel, which in turn calls 'controller_resolver' which calls 'controller_name_resolver'. This class convert the string into a "namespace::method" notation.
Take a look at decorating services to get an idea of how to do it.
Here is an untested class you can work with
class ActionlessNameParser
{
protected $parser;
public function __construct(ControllerNameParser $parser)
{
$this->parser = $parser;
}
public function parse($controller)
{
if (3 === count($parts = explode(':', $controller))) {
list($bundle, $controller, $action) = $parts;
$controller = str_replace('/', '\\', $controller);
try {
// this throws an exception if there is no such bundle
$allBundles = $this->kernel->getBundle($bundle, false);
} catch (\InvalidArgumentException $e) {
return $this->parser->parse($controller);
}
foreach ($allBundles as $b) {
$try = $b->getNamespace().'\\Controller\\'.$controller.'Controller';
if (class_exists($try)) {
// You can also try testing if the action method exists.
return $try.'::'.$action;
}
}
}
return $this->parser->parse($controller);
}
public function build($controller)
{
return $this->parser->build($controller);
}
}
And replace the original service like:
actionless_name_parser:
public: false
class: My\Namespace\ActionlessNameParser
decorates: controller_name_converter
arguments: ["#actionless_name_parser.inner"]
Apparently the Action suffix is here to distinguish between internal methods and methods that are mapped to routes. (According to this question).
The best way to know for sure is to try.
// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class HelloController
{
/**
* #Route("/hello/{name}", name="hello")
*/
public function indexAction($name)
{
return new Response('<html><body>Hello '.$name.'!</body></html>');
}
}
Try to remove the Action from the method name and see what happens.
I have model class that calls mailer class inside one of its methods:
class someModel{
public function sendEmail($data){
$mailer = new Mailer();
$mailer->setFrom($data['from']);
$mailer->setTo($data['to']);
$mailer->setSubject($data['subject']);
return $mailer->send();
}
}
How can I test sendEmail method? Maybe I should mock mailer class and check if all these mailer methods were called in sendMail method?
Your help would be appreciated.
IMO wrapping the Mailer class does not solve the problem you're facing, which is you don't have control over the Mail instance being used.
The problem comes from creating the dependencies inside the object that needs them instead of injecting them externally like this:
class someModel{
private $mailer;
public function __construct(Mailer $mailer) {
$this->mailer = $mailer;
}
public function sendEmail($data){
$this->mailer->setFrom($data['from']);
$this->mailer->setTo($data['to']);
$this->mailer->setSubject($data['subject']);
return $this->mailer->send();
}
}
When creating the someModel instance, you must pass a Mail instance (which is an external dependency). And in the test you can pass a Mail mock that will check that the correct calls are being made.
Alternative:
If you feel that injecting a Mail instance is bad (maybe because there are lots of someModel instances), or you just can't change your code this way, then you could use a Services repository, that will keep a single Mail instance and that allows you to set it externally (again, in the test you would set a mock).
Try a simple one like Pimple.
I would (and have in my own code with Mailer!) wrap your instance of Mailer inside a class that you write. In other words, make your own Email class that uses Mailer under the hood. That allows you to simplify the interface of Mailer down to just what you need and more easily mock it. It also gives you the ability to replace Mailer seamlessly at a later date.
The most important thing to keep in mind when you wrap classes to hide external dependencies is keep the wrapper class simple. It's only purpose is to let you swap out the Email libraries class, not provide any complicated logic.
Example:
class Emailer {
private $mailer = new Mailer();
public function send($to, $from, $subject, $data) {
$this->mailer->setFrom($from);
$this->mailer->setTo($to);
...
return $mailer->send();
}
}
class EmailerMock extends Emailer {
public function send($to, $from, $subject, $data) {
... Store whatever test data you want to verify ...
}
//Accessors for testing the right data was sent in your unit test
public function getTo() { ... }
...
}
I follow the same pattern for all classes/libraries that want to touch things external to my software. Other good candidates are database connections, web services connections, cache connections, etc.
EDIT:
gontrollez raised a good point in his answer about dependency injection. I failed to explicitly mention it, but after creating the wrapper the way you would want to use some form of dependency injection to get it into the code where you want to use it. Passing in the instance makes it possible to setup the test case with a Mocked instance.
One method of doing this is passing in the instance to the constructor as gontrollez recommends. There are a lot of cases where that is the best way to do it. However, for "external services" that I am mocking I found that method became tedious because so many classes ended up needing the instance passed in. Consider for example a database driver that you want to Mock for your tests, but you use in many many different classes. So instead what I do is create a singleton class with a method that lets me mock the whole thing at once. Any client code can then just use the singleton to get access to a service without knowing that it was mocked. It looked something like this:
class Externals {
static private $instance = null;
private $db = null;
private $email = null;
...
private function __construct() {
$this->db = new RealDB();
$this->mail = new RealMail();
}
static function initTest() {
self::get(); //Ensure instance created
$db = new MockDB();
$email = new MockEmail();
}
static function get() {
if(!self::$instance)
self::$instance = new Externals();
return self::$instance;
}
function getDB() { return $this->db; }
function getMail() { return $this->mail; }
....
}
Then you can use phpunit's bootstrap file feature to call Externals::initTest() and all your tests will be setup with the mocked externals!
First, as RyanW says, you should write your own wrapper for Mailer.
Second, to test it, use a mock:
<?php
class someModelTest extends \PHPUnit_Framework_TestCase
{
public function testSendEmail()
{
// Mock the class so we can verify that the methods are called
$model = $this->getMock('someModel', array('setFrom', 'setTo', 'setSubject', 'send'));
$controller->expects($this->once())
->method('setFrom');
$controller->expects($this->once())
->method('setTo');
$controller->expects($this->once())
->method('setSubject');
$controller->expects($this->once())
->method('send');
$model->sendEmail();
}
}
The above code is untested, but it basically mocks the someModel class, creating dummy functions for each each function called within sendEmail. It then tests to make sure each of the functions called by sendEmail is called exactly once when sendEmail is called.
See the PHPUnit docs for more info on mocking.
I am trying to run a parametrized tests... Was trying to implement it like it explained here:
http://docs.flexunit.org/index.php?title=Parameterized_Test_Styles
Here is what my test case looking
import org.flexunit.runners.Parameterized;
[RunWith("org.flexunit.runners.Parameterized")]
public class ArrayBasedStackTests
{
[Paremeters]
public static var stackProvider:Array = [new ArrayBasedStack(), new LinkedListBasedStack()] ;
private var _stack:IStack;
public function ArrayBasedStackTests(param:IStack)
{
_stack = param;
}
[Before]
public function setUp():void
{
}
[After]
public function tearDown():void
{
}
[Test ( description = "Checks isEmpty method of the stack. For empty stack", dataProvider="stackProvider" )]
public function isEmptyStackPositiveTest():void
{
var stack:IStack = _stack;
assertEquals( true, stack.isEmpty() );
}
But this code throws following initializing Error:
Error: Custom runner class org.flexunit.runners.Parameterized should
be linked into project and implement IRunner. Further it needs to have
a constructor which either just accepts the class, or the class and a
builder.
Need help to fix it
UPDATE
I've updated the code so it looks like this
[RunWith("org.flexunit.runners.Parameterized")]
public class ArrayBasedStackTests
{
private var foo:Parameterized;
[Parameters]
public static function stacks():Array
{
return [ [new ArrayBasedStack()], [new LinkedListBasedStack()] ] ;
}
[Before]
public function setUp():void
{
}
[After]
public function tearDown():void
{
}
[Test ( description = "Checks isEmpty method of the stack. For empty stack", dataProvider="stacks")]
public function isEmptyStackPositiveTest(stack:IStack):void
{
assertEquals( true, _stack.isEmpty() );
}
It works. But the result is a bit strange. I have 4 test executed instead of 2. (I have 2 items in data provider, so cant get why do I have 4 tests).
Output
http://screencast.com/t/G8DHbcjDUkJ
The [Parameters] meta-data specifies that the parameters are passed to the constructor of the test - so the test class is called for each parameter. You also have the dataProvider set for the specific test method, so the test method is also called once for each parameter. Two calls for the test, and two calls to the method, ends up running four tests.
The solution is to either use [Parameters] meta-tag which specifies the data to use for the whole test class, or use the dataProvider for each test method, but not both with the same data at the same time.
You're missing the static reference to Paramaterized, as shown here:
import org.flexunit.runners.Parameterized;
[RunWith("org.flexunit.runners.Parameterized")]
public class MyTestNGTest
{
private var foo:Parameterized;
...
Basically, that error means that the [Runner] defined isn't available at runtime, which occurs if there is no static reference in the class to cause it to get linked in.
In FlexUnit 4.5.1, this approach changed to using [Rule]'s like so:
public class MyTestNGTest
{
[Rule]
public function paramaterizedRule:ParamaterizedRule = new ParamaterizedRule();
...
}
However, I can't seem to see an actual implementation of IMethodRule for paramaterized tests (that example is fictional).