Instantiating a class based on paramters in PHP - php

I have a slightly different problem where I'm instantiating a class from within another class which works when I manually invoke the method but doesn't when I automate it. The code is below:
class Button {
function __construct($params = Array()) {
// get some common properties
}
}
class HelperButton extends Button {
function __construct($params = Array()) {
parent::__construct($params);
}
function getHelperButton() {
//generate helper button code
}
}
I'm using the above class in the following classes
include_once('field.class.php');
include_once('buttons.class.php');
class Field {
function __construct() {
}
function setName($name){
$this->name = $name;
}
}
class TextField extends Field {
function __construct() {
parent::__construct();
}
function setFiller($fill = "") {
$helperbtn = new HelperButton($fill);
$this->helperbtn = $helperbtn->getHelperButton();
}
function getTextField(){
$this->textfield = "<input name='blah' />".$this->heperbtn;
return $this->textfield;
}
}
class Segment extends Field {
function __construct() {
parent::__construct();
}
function addTextField($params = array()) {
if(is_array($params)){
$txtfld = new TextField();
}
if (isset($params['type']['filler'])) {
$txtfld->setFiller($params['type']['fill']);
}
$this->segment .= $txtfld->getTextField();
}
function addFillerField($params = array()) {
$params['type']['filler'] = true;
$this->addTextField($params);
}
}
Now, I'm generating the text field in my page dynamically with params. The following code works well:
$segment = new Segment();
$segment->addFillerField("type"=>array("filler"=>true,"fill"=>"sometext")); //this should add a button next to the field which will open a window that will load the 'fill' text
$segment->render(); // this will print the segment output to screen
However, the following code does not work. It prints the text field alright but it does not print the button:
$fldmthds = array("FLRFLD" => "FillerField");
$pagedtl = array("FCLSID" => "FLRFLD", "FLDNAM" => "Field Name", "FLDTYP" => "filler:true,fill:sometext");
$mysegment = new Segment();
foreach ($pagedtl as $flds => $val) {
$mthd = "add" . $fldmthds[$val['FCLSID']]; // resolves to addFillerField
$params = array(
'name' => $val['FLDNAM'],
'type' => json_decode("{" . $val['FLDTYP'] . "}", true)
);
$mysegment->{$mthd}($params);
}
$mysegment->render();
PHP did not give any error/warning. It just goes silent and forgets the button. Does anyone see what is going wrong here? Thanks for your time.

Can't comment for now... just one thing: json_decode() will fail. To receive valid json change {filler:true,fill:sometext} to {"filler": true, "fill": "sometext"}

Related

How to output value of array which is set by php class?

I hope someone can help me with my problem. I'm still a beginner in PHP so sorry.
My code looks like this:
class Component
{
public $title;
// value if nothing gets set
public function __construct($title = "Test") {
$this->title = $title;
}
public function setTitle($value)
{
$this->title = $value;
}
public function getTitle()
{
return "Title: ".$this->title;
}
public function returnInfo()
{
$info = array(
'Titel' => $this->title,
);
return $info;
}
So in the class "Component" the functions should set and get a specific value. If nothing is set for a.e. title it should get the value "Test". With returnInfo() the informations like title should get returned.
My other class (where someone can add the informations like title) looks like this:
abstract class ComponentInfo extends Component
{
protected function getComponentInfo ()
{
$button1 = new Component;
$button1->setTitle("Button-Standard");
// should return all infos for button1
$button1Info = $button1->returnInfo();
foreach ($button1Info as $info)
{
echo ($info);
}
}
}
So it should work like this: in a other class named ComponentInfo a user can add a component like a button. Then the user can set informations like the title. And after that the information should get saved in an array and now I want to display all informations like this:
Title: Button-Standard
How can it work? And where is the mistake in my code?
It would be helpful to get a working code where the user can make as much ComponentInfo classes he want and where he can add different components with information that can be saved into an array.
And at the end it should get outputed as text on a main page.
You can't instantiate an abstract class. You need to remove the abstract keyword from the ComponentInfo class.
UPDATE given the info in your comment, I'd go this
Component.php
abstract class Component
{
private $key;
private $title;
public function __construct($key, $title) {
$this->setKey($key);
$this->setTitle($title);
}
public function setKey($key)
{
$this->key = $key;
}
public function getKey()
{
return $this->key;
}
public function setTitle($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
public function __toString()
{
return $this->getKey().': '.$this->getTitle();
}
}
ComponentInfo.php
class ComponentInfo extends Component
{
public function __construct($key='Info', $title='example title')
{
parent::__construct($key, $title);
}
}
And then use it in your code
somefile.php
$components = [];
$components []= new ComponentInfo();
$components []= new ComponentInfo('Different Key', 'Other info');
$components []= new ComponentNavigation('longitude', 'latidude'); //create another class like ComponentInfo
[... later you want to print this info in a list for example]
echo '<ul>';
foreach($components as $components) {
echo '<li>'.$component.'</li>'; //the __toString() method should get called automatically
}
echo '</ul>';
This should work, however, having different components with no other specificities than a different title and key is pointless. Instead, you could simply have different Components with a different key and title.

How to test specific methods with PHPUnit

I need help with PHPUnit and some methods. How should you guys write tests in PHPUnit to reach a high code coverage for the following properties and methods?
I'm pretty new to PHPUnit and could need some help. I've just write some test cases for more basic code. This class generates flash messages for the end user, and store it in a session.
Extremely grateful for some help. Any ideas?
private $sessionKey = 'statusMessage';
private $messageTypes = ['info', 'error', 'success', 'warning']; // Message types.
private $session = null;
private $all = null;
public function __construct() {
if(isset($_SESSION[$this->sessionKey])) {
$this->fetch();
}
}
public function fetch() {
$this->all = $_SESSION[$this->sessionKey];
}
public function add($type = 'debug', $message) {
$statusMessage = ['type' => $type, 'message' => $message];
if (is_null($this->all)) {
$this->all = array();
}
array_push($this->all, $statusMessage);
$_SESSION[$this->sessionKey] = $this->all;
}
public function clear() {
$_SESSION[$this->sessionKey] = null;
$this->all = null;
}
public function html() {
$html = null;
if(is_null($this->all))
return $html;
foreach ($this->all as $message) {
$type = $message['type'];
$message = $message['message'];
$html .= "<div class='message-" . $type . "'>" . $message . "</div>";
}
$this->clear();
return $html;
}
I have setup an setup-case, like this:
protected function setUp() {
$this->flash = new ClassName();
}
Also tried one test case:
public function testFetch() {
$this->assertEquals($this->flash->fetch(), "statusMessage", "Wrong session key.");
}
But gets an error message telling me: "Undefined variable: _SESSION"
If I then try:
public function testFetch() {
$_SESSION = array();
$this->assertEquals($this->flash->fetch(), "statusMessage", "Wrong session key.");
}
I get another error message telling: "Undefined index: statusMessage"
Try something like this:
function testWithoutSessionKey() {
$_SESSION = array();
$yourClass = new YourclassName();
$this->assertNull($yourClass->html()); }
function testWithSomeSessionKey() {
$_SESSION = array( 'statusMessage' => array(...));
$yourClass = new YourclassName();
$this->assertSame($expect, $yourClass->html());
}
You can't instantiate your class in the setup because your constructor need that the SESSION variable may exist(so you can test that can have some value inside).
You can evalutate (assert) only the ouptput of a method, so you can't assert that the message of the return of the method fetch.
In your method testFecth you have found a bug! Thanks to the test for this. Try fixing it with checking as you do in the construct :
public function fetch() {
if (isset($_SESSION[$this->sessionKey]))
$this->all = $_SESSION[$this->sessionKey];
}
Hope this help

Modx: getCollection query is not working

Inside my processor class I have a statement that grabs all the projects from a db table and formats them to be displayed. This method does not work and halts at the getCollection call.
class GlobalLinkSettingsProcessor extends modObjectGetListProcessor{
public function initialize() {
return parent::initialize();
}
public function process() {
$result = $this->modx->getCollection('ManagerProjects');
$project_names = array();
foreach ($result as $row) {
$projects = unserialize($row->get('manager_projects'));
foreach($projects as $short_code => $project) {
$project_names[] = array('project_name' => $project, 'project_short_code' => $short_code);
}
}
return '{"total":' . count($project_names) . ',"results":' . $this->modx->toJSON($project_names) . ',"success":true}';
}
...
}
This code that uses plain SQL does work:
class GlobalLinkSettingsProcessor extends modObjectGetListProcessor{
public function initialize() {
return parent::initialize();
}
public function process() {
$leadersql = "SELECT * FROM `modx_manager_projects`";
$query = $this->modx->query($leadersql);
$project_names = array();
while ($row = $query->fetch(PDO::FETCH_ASSOC)) {
$projects = unserialize($row['manager_projects']);
foreach($projects as $short_code => $project) {
$project_names[] = array('project_name' => $project, 'project_short_code' => $short_code);
}
};
return '{"total":' . count($project_names) . ',"results":' . $this->modx->toJSON($project_names) . ',"success":true}';
}
...
}
I use similar method to the first which saves ManagerProjects and works fine, so I don't think it has to do with the model declaration. I could easily just use the second method above since it seems to work, but I want to use the best method.
What is wrong with the first method?
Is the first method the proper way to implement SQL in the Modx processor? Or is there a better way?
We can do this task easier a little bit.
#Vasis is right but we can use base prepareRow method instead of reloading iterate method:
<?php
class GlobalLinkSettingsProcessor extends modObjectGetListProcessor{
public $classKey = 'ManagerProjects';
protected $projects = array();
public function prepareRow(xPDOObject $object) {
$_projects = unserialize($object->get('manager_projects'));
foreach($_projects as $short_code => $project) {
$this->projects[] = array('project_name' => $project, 'project_short_code' => $short_code);
}
return parent::prepareRow($object);
}
public function outputArray(array $array,$count = false) {
$count = count($this->projects);
return parent::outputArray($this->projects,$count);
}
}
return 'GlobalLinkSettingsProcessor';
There we can see one of modx ‘features’. In modObjectGetListProcessor process method we can see this:
public function process() {
$beforeQuery = $this->beforeQuery();
if ($beforeQuery !== true) {
return $this->failure($beforeQuery);
}
$data = $this->getData();
$list = $this->iterate($data);
return $this->outputArray($list,$data['total']);
}
getData method returns a list of objects and it goes to iterate method (where we can check if the object is accessible and change the list of these objects on demand). If you don't have access to some of objects we'll get changed list. And it goes to outputArray method but second parameter is still old for it. So you should count them again.
This is solution is quite well but you tried to get data which is stored in object's field. So afterIteration method will be unusable for further extension in my version of processor. But who cares? :)
P.S.: About your first version of processor. modObjectGetList processor is ready for getting collection. So you have not to use getcollection method. Just add proper classKey property to it.
Another way is in modProcessor extension. It gives to you a base structure. But you can make your own kind of stuff.
Because you do it wrong! Just see this. The right way to do it, is something like this:
<?php
class GlobalLinkSettingsProcessor extends modObjectGetListProcessor{
public $classKey = 'ManagerProjects';
public function iterate(array $data) {
$list = array();
$list = $this->beforeIteration($list);
$this->currentIndex = 0;
/** #var xPDOObject|modAccessibleObject $object */
foreach ($data['results'] as $object) {
if ($this->checkListPermission && $object instanceof modAccessibleObject && !$object->checkPolicy('list')) continue;
$projects = unserialize($object->get('manager_projects'));
foreach($projects as $short_code => $project) {
$objectArray = array('project_name' => $project, 'project_short_code' => $short_code);
if (!empty($objectArray) && is_array($objectArray)) {
$list[] = $objectArray;
$this->currentIndex++;
}
}
}
$list = $this->afterIteration($list);
return $list;
}
}

How to make an action for missing actions?

I am very new to cakePHP.
I am working on a controller like so:
class DesignersController extends AppController
{
var $name = 'Designers';
function index()
{
$data = $this->Designer->find('all');
$this->set('designers', $data);
}
function belle_etoile()
{
$this->show_designer("belle etoile");
}
function ritani()
{
$this->show_designer("ritani");
}
function swarovski()
{
$this->show_designer("swarovski");
}
function verragio()
{
$this->show_designer("verragio");
}
private function show_designer($designer)
{
$this->layout = 'first';
$data = $this->Designer->find('first', array('conditions' => array('Designer.name' => $designer)));
$this->set('data', $data);
$this->render('show_designer');
}
}
As you can see many of the "actions" are shortcuts for show_designer/param action where param is the name of the shortcut action.
Every one of these actions is a "designer" in the database. I just don't want to have to make the url designers/show_designer/ritani, I would rather it just be designers/ritani.
This works, but the problem is:
I have to create a bunch of redundant functions for every designer, and if a new designer gets added, it won't work until I add a function for it.
I would rather have a function/action that runs if the action requested is missing, and has the action that was requested as a parameter
so if I request url designers/stardust, since stardust is not defined as an action it would call the catch_all action with stardust as the parameter.
So instead of a bunch of redundant functions I could just have this:
function catch_all($action)
{
$this->show_designer($action)
}
Is there anyway to do something like this?
Use routing instead
// add this to app/config/routes.php
Router::connect('/designer/*', array('controller' => 'designers', 'action' => 'designer'));
In your controller
// and remove all actions 'belle_etoile', 'swarovski' etc
// change `show_designer` to `public designer`
class DesignersController extends AppController {
var $name = 'Designers';
function designer($name)
{
$this->layout = 'first';
$data = $this->Designer->find('first', array('conditions' => array('Designer.name' => $name)));
if(!empty($data)) {
$this->set('data', $data);
$this->render('show_designer');
} else {
$this->redirect('index');
}
}
}
have you tried adding a call method:
function __call($action,$params = array())
{
$this->show_designer($action)
}
Im not 100% shore how cake calls its methods but it should work:
Example of the usage:
finale class Test
{
function __call($action,$params = array())
{
echo $action . " called:<br />";
foreach($params as $param)
{
echo "Param: "$param . "<br />";
}
}
}
$test = new Test();
$test->SomeNonExistantmethod("param 1","param 2");
This would output:
SomeNonExistantmethod called:
param: param 1
param: param 2
your class would be like so:
class DesignersController extends AppController
{
var $name = 'Designers';
var $allowed = array(
"belle_etoile",
"ritani",
"swarovski",
"verragio"
);
function index()
{
$data = $this->Designer->find('all');
$this->set('designers', $data);
}
function __call($action,$params = array())
{
if(in_array($action,$this->allowed))
{
$this->show_designer($action);
}
}
private function show_designer($designer)
{
$this->layout = 'first';
$data = $this->Designer->find('first', array('conditions' => array('Designer.name' => $designer)));
$this->set('data', $data);
$this->render('show_designer');
}
}

How to get the name of the calling class (in PHP)

define('anActionType', 1);
$actionTypes = array(anActionType => 'anActionType');
class core {
public $callbacks = array();
public $plugins = array();
public function __construct() {
$this->plugins[] = new admin();
$this->plugins[] = new client();
}
}
abstract class plugin {
public function registerCallback($callbackMethod, $onAction) {
if (!isset($this->callbacks[$onAction]))
$this->callbacks[$onAction] = array();
global $actionTypes;
echo "Calling $callbackMethod in $callbacksClass because we got {$actionTypes[$onAction]}" . PHP_EOL;
// How do I get $callbacksClass?
$this->callbacks[$onAction][] = $callbackMethod;
}
}
class admin extends plugin {
public function __construct() {
$this->registerCallback('onTiny', anActionType);
}
public function onTiny() { echo 'tinyAdmin'; }
}
class client extends plugin {
public function __construct() {
$this->registerCallback('onTiny', anActionType);
}
public function onTiny() { echo 'tinyClient'; }
}
$o = new core();
$callbacksClass should be admin or client. Or am I missing the point here completely and should go about this another way? It should be noted that I will only accept an answer that does not require me to send the classname as an argument to the registerCallback method.
If anyone came here looking for how to get the name of a calling class from another class like I did, check this out https://gist.github.com/1122679
EDIT: pasted code
function get_calling_class() {
//get the trace
$trace = debug_backtrace();
// Get the class that is asking for who awoke it
$class = $trace[1]['class'];
// +1 to i cos we have to account for calling this function
for ( $i=1; $i<count( $trace ); $i++ ) {
if ( isset( $trace[$i] ) ) // is it set?
if ( $class != $trace[$i]['class'] ) // is it a different class
return $trace[$i]['class'];
}
}
EG
class A {
function t() {
echo get_calling_class();
}
}
class B {
function x() {
$a = new A;
$a->t();
}
}
$b = new B;
$b->x(); // prints B
Use get_class():
$this->callbacks[$onAction][] = $callbackMethod;
$className = get_class($this);
// Call callback method
$className->$callbackMethod();
You should really do something like:
$this->registerCallback(array($this, 'onTiny'), anActionType);
That is how PHP works with handles to object methods.
From PHP 8+ you can use static::class rather than get_class($this).
This one is also auto-fixed with PHP Code Sniffer and rule SlevomatCodingStandard.Classes.ModernClassNameReference

Categories