Updating an object in a Collection - php

I'm using a Collection class to store objects of 'players'. Now in my Bet class I want to update the points of one of the players.. But I think I'm only updating it in once instance of my Collection. I'm not quite sure how to talk to the global collection object. Here's my code
Index.php I'm creating a new PlayerCollection and passing it to my Parser class
$app->get('/', function () use ($twig) {
try {
$players = new PlayerCollection();
$matchParser = new MatchParser($players);
$matches = new MatchCollection($matchParser->parse('../data/data.json'));
} catch(JsonException $e) {
die($e->getMessage());
}
echo $twig->render('home.html', [
'matches' => $matches->all(),
'players' => $players
]);
});
Parser Class I'm accepting the PlayerCollection in my constructor and setting the Player object in the Bet class using the following code.
The this->players->find() functionality is tested and returns the correct object from the collection
$bet = new Bet();
$bet->setPlayer($this->players->find($name));
Bet Class
class Bet {
private $player;
private $teamHome;
private $teamAway;
private $scoreHome;
private $scoreAway;
public function getPoints($scoreHome, $scoreAway)
{
if($scoreHome == $scoreAway ) {
$this->player->setPoints(100);
}
}
}
I call the function to calculate the score inside my Twig template
{{ bet.getPoints(match.getHomeScore(), match.getAwayScore()) }}
When I echo out the score of the player inside my bet class it's working.. But my PlayerCollection hasn't updated on my index.php page.
It might be my Template? The value's I want to update are already placed by twig, only later in the template I call the method to update the variables previous, but already placed
Any idea what I might be doing wrong? Thank you!

when you created your new Bet() object, you copied an existing instance of one of your player objects into your Bet object, thus not altering the original one.
code that was previously here didn't make sense, oops

Related

Zend Framework 3, why can't I bind doctrine entities to forms in the factory?

Have run into a curious behavior a few times over the years, have always meant to ask about it.
It has to do with a behavior I don't understand around binding objects to forms in Zend Framework.
Consider this factory which builds a form, loads a Doctrine entity from database, and attempts to bind it to the form (so that the values display on render):
class TermsConfigFormFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$form = new TermsConfigForm('terms_config_form', $options);
$form->setInputFilter($container->get('InputFilterManager')->get(TermsConfigInputFilter::class, $options));
$form->setHydrator(new DoctrineHydrator($container->get('doctrine.entitymanager.orm_default'), false));
if (!is_string($options['locale'])) {
throw new \InvalidArgumentException("An illegal locale variable was received by the terms configuration factory");
}
$termsConfig = $container->get(TermsConfigMapper::class)->get($options['locale']);
if (!$termsConfig) {
$termsConfig = new TermsConfig($options['locale']);
}
// A. If we do just this, the form doesn't print data
$form->bind($termsConfig);
return $form;
}
}
The form is well wired through form_elements, and so forth. We then attempt to use it inside a Controller like so:
$termsForm = $this->formElementManager->get(TermsConfigForm::class, ['locale' => $this->locale()]);
$viewModel->setVariable('termsForm', $termsForm);
Interestingly, we find that the object will not show the bound data in the ViewModel. Now, even more curious, is if we remove the call to "bind" in the factory and do this in the controller instead, the values are properly displayed!!
$termsForm = $this->formElementManager->get(TermsConfigForm::class, ['locale' => $this->locale()]);
// B. You have to do this in the controller, here, for it to print data!
$termsForm->bind($termsForm->getObject());
$viewModel->setVariable('termsForm', $termsForm);
Why does it not work within the factory?
From this vantage point, the call to bind in the controller, is analogous to the call to bind in the Factory. I'd like to keep this stuff in the factory, but seems I cannot!
Looks like the intricacy lies with the FormElementManager that stacks the factory and the init cycle in order. In other words, init is not called until the factory has completed its work.
Paraphrasing, calling "bind" within a form's factory does not exhibit the same behavior as calling "bind" after the factory returns the form because the Factory calls init (automatically) in between.
Be careful of this 'order of operations' trap.
If you look at the Factory pattern you'll notice that the job of the factory pattern is to return the desired object only. It has nothing to do with the manipulation of the object. So, with that said all the things you want to do with the desired object should be done in the model object. As the model object can be as fat as it can be. Therefore, I would suggest moving your binding of the desired object out from the factory and move it to the model.
I'll quote something which I saw in a video and Marco Pivetta showed something in a very abstract way and that was something like below:
// The below code is my understanding of Marco Pivetta explaining in a youtube video.
use Psr\Container\ContainerInterface;
class TermsConfigFormFactory implements FactoryInterface
{
$form = new TermsConfigForm('terms_config_form', $options);
$form->setInputFilter($container->get('InputFilterManager')->get(TermsConfigInputFilter::class, $options));
$form->setHydrator(new DoctrineHydrator($container->get('doctrine.entitymanager.orm_default'), false));
/*
This should have been the first line in the method, as it looks like. Because it seems you want locale to be given.
if (!is_string($options['locale'])) {
throw new \InvalidArgumentException("An illegal locale variable was received by the terms configuration factory");
}
//From here till binding should be moved to a model.
$termsConfig = $container->get(TermsConfigMapper::class)->get($options['locale']);
if (!$termsConfig) {
$termsConfig = new TermsConfig($options['locale']);
}
// A. If we do just this, the form doesn't print data
$form->bind($termsConfig);
*/
return $form;
}
This is what Marco Pivetta would have done in my opinion according to his video and what I've abstracted.
class TermsConfigFormFactory implements FactoryInterface
{
if (!is_string($options['locale'])) {
throw new \InvalidArgumentException("An illegal locale variable was received by the terms configuration factory");
}
$form = new TermsConfigForm('terms_config_form', $options);
$form->setInputFilter($container->get('InputFilterManager')->get(TermsConfigInputFilter::class, $options));
$form->setHydrator(new DoctrineHydrator($container->get('doctrine.entitymanager.orm_default'), false));
return $form;
}
// Some model
namespace Mynacespace\Model;
class SomeModel extends /* I don't remember the actual FQCN*/DoctirneRepository
{
public function dosomething(Form $form ){
$request = $this->getServiceContainer()->getRequest();
$data = $request->getPost();
$form->bind($data);
// do more of your work here.
// This array returning was not mentioned in Marco video.
return ['status' => 200, 'message' => 'Success', 'data' => 'form' => $form];
}
}

Calling methods on object gets 'call to member function on array' error

I inherited this project from my predecessor, and he was way overqualified. A lot of stuff he wrote goes over my head. But as far as vanilla php goes, I'm pretty confident, and can't for the life of me figure out why the application thinks the object I created is an array. Maybe I don't actually know anything. You tell me.
use via\zoom\Bulletin;
use via\zoom\DatabaseConnection;
require_once('includes/config.php');
require_once(CORE .'sql.php');
require_once(CORE . 'model.php');
require_once(CORE . 'bulletin.php');
// If we've passed the validation step we can guarantee we have a valid $active_user
validate();
//run if a page deletion has been requested
if (isset($_GET['delpage'])) {
$del = $_GET['delpage'];
$bulletin = new Bulletin;
$bulletin = Bulletin::get($del);
if(!empty($bulletin))
{
$bulletin->delete();
/*
So.
For some reason, the above object is cast as an array.
If you try to cast it as an object, it defaults to stdClass.
On the left we have a method complaining that it can't work outside of its class. Hard stop, array to method exception.
On the right we have an object with all the right data, but set to the wrong class, so it can't find the delete method at all. Hard stop, undefined method exception.
*/
//this is the workaround, pulled the script straight from the delete method in the model class
/*$dbh = DatabaseConnection::get();
$query_string = "DELETE FROM brochure_generator_bulletin WHERE id = $del";
try {
$dbh->query($query_string);
//return true;
} catch (\Exception $e) {
//return false;
}*/
}
header('Location: bulletins');
exit();
}
Here's the get method from the Bulletin class, extends Model--
public static function get( ...$ids )
{
$matches = parent::get( ...$ids );
foreach( $matches as &$match )
{
$match->content = json_decode( $match->content );
}
return $matches;
}
And here's the delete method from the Model Class:
public function delete()
{
if (isset($this->id)) {
$dbh = DatabaseConnection::get();
$query_string = "DELETE FROM {$this->table_name} WHERE id = \"{$this->id}\"";
try {
$dbh->query($query_string);
return true;
} catch (\Exception $e) {
return false;
}
}
return false;
}
What am I missing? Is he using a framework I'm not familiar with? I'm utterly grasping at straws here, and at this point my options are grab all the method scripts and stick them where they need to be inline, or just starting over from the ground up.
You don't need to create a new Bulletin object before using the static get() method, so you can remove this:
$bulletin = new Bulletin;
That $bulletin variable is immediately overwritten by the next line anyway.
$bulletin = Bulletin::get($del);
get() takes one or more ids and returns an array of one or more corresponding objects. You're giving it one id and expecting one object back, but it's still going to return that object inside an array. You just need to get the object out of the array so you can call its delete method.
if(!empty($bulletin))
{
$bulletin = reset($bulletin); // get the first item in the array
$bulletin->delete();
You could also review the model and see if it has a different method that returns a single object rather than an array of objects.

Behat - Using an example table String in an xpath selector array

I have been using Behat for a year or so at a level fine for the automation of most websites but I now need to start using it more for user generated content, I am relatively new to PHP and at the moment I am struggling how to use a String entered in an Example table in an x-path array:
Feature: Campaign
Scenario Outline: Pass campaign string to xpath array
Then I add a new campaign name of "<campaign>"
Examples:
|campaign |
|Automation|
The context file looks like this
/**
* #Then /^I add a new campaign name of "([^"]*)"$/
*/
public function iAddANewCampaignNameOf($campaign)
{
/**
* #var CreateCampaign $createCampaign
*/
$createCampaign= $this->getPage('CreateCampaign');
$createCampaign->campaignName($campaign);
}
Then I use the Page Object extension for the class Campaign.php
class CreateCampaign extends AutomationPage
{
protected $path = 'someURL';
public $campaign;
protected $elements = array(
'campaignHeader' => array('xpath' => "//*[#id='site-navigation-campaigns']"),
);
public function campaignName ($campaign)
{
$this->campaign = $campaign;
$this->getSession()->wait(5000);
$this->getElement('campaignName')->setValue($campaign);
}
So far so good, the tester can enter a campaign name of "Automation" - it gets passed through the context file and the campaign name is set in the browser.
What I am lacking is to be able to retain this $campaign name string and use it in another page so I can reference it in another array i.e. for selecting an existing campaign as follows:
SecondPageObjectPage.php
class ReferenceCampaign extends AutomationPage
{
protected $path = 'someURL';
protected $elements = array(
'referenceCampaign' => array('xpath' => "//*[contains(#id,'***HERE I NEED TO GET THE
$campaign value"),
);
public function editExistingCampaign ($campaign)
{
$this->getElement('referenceCampaign')->click();
}
}
I have tried my best to simplify things and I can explain further if any of this isnt clear - hopefully its just a simple PHP question and not really Behat specific
Thanks Ian
Your example is a much better way of doing things, I have only recently started using partial contains and it expands the flexibility of finding stubborn xpaths especially if you combine more than one, like the working example below:
public function editExistingCampaign ($campaign)
{
$this->getSession()->wait(5000);
$element = $this->find('xpath', '//*[contains(#id,"'.$campaign.'")]
[contains(#id,"actionbuttons")]');
if (isset($element)) {
$element->click();
} else {
throw new Exception('Element not found');
}
}
The only slight change was to add a ] at the end of the x-path
I'm sure it's a simple question, but I think that I am missing a point. If all you want is to get hold of the value that was used on the page then you need to review your code structure. First, you cannot pass method argument to the property definition in another class, but you can find the element inside editExistingCampaign.
class ReferenceCampaign extends AutomationPage
{
protected $path = 'someURL';
public function editExistingCampaign ($campaign)
{
$element = $this->find('xpath', '//*[contains(#id, "' . $campaign . '")]');
if (isset($element)) {
$element->click();
} else {
throw new Exception('Element not found');
}
}
}
I'm assuming you are using Symfony Page Object extension, which you should mention. I'm not sure if I've got the syntax right, but the idea is to find your element inside the method.

Unable to access variable from included file

As the title says there is a problem accessing variable (associative array) inside class from included file. Here is the source code both class and include file:
require("applications/cw_database.php");
require("config/dbConfig.php");
require("config/appConfig.php");
class APP_ASSESMENTS
{
private $dbObj;
private $DisplayOutput = "";
public function __construct($PageParams)
{
try
{
$dbObj = new CW_DB($dbConfig['hostname'],$dbConfig['username'],$dbConfig['password'],$dbConfig['name'],$dbConfig['port']);
} catch (Exception $e) {
throw new ErrorException($e);
}
}
...
The other part has nothing to do with $dbConfig.
Also this is the included file (config/dbConfig.php):
/*
Testing configuration for MySQL database
*/
$dbConfig['username'] = "phpcoursework"; // changed on demand
$dbConfig['password'] = "phpcoursework"; // changed on demand
$dbConfig['hostname'] = "localhost"; // changed on demand
$dbConfig['name'] = "students"; // changed on demand
$dbConfig['port'] = 3306; // default for MySQL
First, $dbObj will not automatically assume class member scope, it will create a local copy of CW_DB and discard it when __construct returns. You need to explicitly reference the property;
$this->dbObj = ...
Anyway, global state using global as suggested by others will "work", but if you're using OOP practices you're best not to do that. You can actually return from an include(), so an option would be to do the following:
// your config file dbConfig.php
return array(
'username' => "phpcoursework",
'password' => "phpcoursework",
'hostname' => "localhost",
'name' => "students",
'port' => 3306,
);
And inject it into the object, via constructor or method (here's constructor)
class APP_ASSESMENTS
{
private $dbObj;
public function __construct($dbConfig, $PageParams)
{
$dbObj = new CW_DB($dbConfig['hostname'], $dbConfig['username'],
$dbConfig['password'], $dbConfig['name'], $dbConfig['port']);
// ...
}
}
// include() here, will actually return the array from the config file
$appAssesments = new \APP_ASSESMENTS(include('dbConfig.php'), $PageParams);
It would be recommended that you go another level up: instead, inject the database object itself, taking the dependency out of your APP_ASSESSMENTS class.
(Also, PascalCase is the typical convention of class naming, such as AppAssessments and CwDb)
$dbObj = new CwDb(include('dbConfig.php'));
$appAssessments = new AppAssessments($dbObj, $etc, $etc);
This simple change allows you to remove the dependency from AppAssessments on CwDb. That way, if you extend CwDb for some reason, you can just pass in an instance of the extended class without having to change any code in AppAssessments
You can change the AppAssessments constructor like so:
public function __construct(CwDb $db, $etc, $etc){
$this->db = $db;
// ...
}
This takes advantage of PHPs (limited, albeit still useful) type-hinting, ensuring the first argument is always of the correct type.
This plays into part of the open/closed principle: classes should be open to extension but closed for modification.
Includes are used in the scope of access. So, to access the variables you need to include the files within your class. As earlier mentioned global will let you access variables from another scope too. But global should be used with caution! See the documentation.
See the manual for more information.
Edit: I need to make it clear that globals are never a good alternative for handling these kind of critical variables..
public function __construct($PageParams){
global $dbConfig;
try{
$dbObj = new CW_DB($dbConfig['hostname'],$dbConfig['username'],$dbConfig['password'],$dbConfig['name'],$dbC onfig['port']);
} catch (Exception $e) {
throw new ErrorException($e);
}
}
or you could use $GLOBALS['dbConfig'].

Problems with redeclaring classes when using PHP's class_alias in a functional test

I'm using PHP 5.3's class_alias to help process my Symfony 1.4 (Doctrine) forms. I use a single action to process multiple form pages but using a switch statement to choose a Form Class to use.
public function executeEdit(sfWebRequest $request) {
switch($request->getParameter('page')) {
case 'page-1':
class_alias('MyFormPage1Form', 'FormAlias');
break;
...
}
$this->form = new FormAlias($obj);
}
This works brilliantly when browsing the website, but fails my functional tests, because when a page is loaded more than once, like so:
$browser->info('1 - Edit Form Page 1')->
get('/myforms/edit')->
with('response')->begin()->
isStatusCode(200)->
end()->
get('/myforms/edit')->
with('response')->begin()->
isStatusCode(200)->
end();
I get a 500 response to the second request, with the following error:
last request threw an uncaught exception RuntimeException: PHP sent a warning error at /.../apps/frontend/modules/.../actions/actions.class.php line 225 (Cannot redeclare class FormAlias)
This makes it very hard to test form submissions (which typically post back to themselves).
Presumably this is because Symfony's tester hasn't cleared the throughput in the same way.
Is there a way to 'unalias' or otherwise allow this sort of redeclaration?
As an alternate solution you can assign the name of the class to instantiate to a variable and new that:
public function executeEdit(sfWebRequest $request) {
$formType;
switch($request->getParameter('page')) {
case 'page-1':
$formType = 'MyFormPage1Form';
break;
...
}
$this->form = new $formType();
}
This doesn't use class_alias but keeps the instantiation in a single spot.
I do not know for sure if it is possible, but judging from the Manual, I'd say no. Once the class is aliased, there is no way to reset it or redeclare it with a different name. But then again, why do use the alias at all?
From your code I assume you are doing the aliasing in each additional case block. But if so, you can just as well simply instantiate the form in those blocks, e.g.
public function executeEdit(sfWebRequest $request) {
switch($request->getParameter('page')) {
case 'page-1':
$form = new MyFormPage1Form($obj);
break;
...
}
$this->form = $form;
}
You are hardcoding the class names into the switch/case block anyway when using class_alias. There is no advantage in using it. If you wanted to do it dynamically, you could create an array mapping from 'page' to 'className' and then simply lookup the appropriate class.
public function executeEdit(sfWebRequest $request) {
$mapping = array(
'page-1' => 'MyFormPage1Form',
// more mappings
);
$form = NULL;
$id = $request->getParameter('page');
if(array_key_exists($id, $mapping)) {
$className = $mapping[$id];
$form = new $className($obj);
}
$this->form = $form;
}
This way, you could also put the entire mapping in a config file. Or you could create FormFactory.
public function executeEdit(sfWebRequest $request) {
$this->form = FormFactory::create($request->getParameter('page'), $obj);
}
If you are using the Symfony Components DI Container, you could also get rid of the hard coded factory dependency and just use the service container to get the form. That would be the cleanest approach IMO. Basically, using class_alias just feels inappropriate here to me.
function class_alias_once($class, $alias) {
if (!class_exists($alias)) {
class_alias($class, $alias);
}
}
This doesn't solve the problem itself, but by using this function it is ensured that you don't get the error. Maybe this will suffice for your purpose.

Categories