I have a Thread entity which has a OneToMany association with a Message entity. I am fetching a thread with a DQL query, and I want to limit its amount of messages to 10. Therefore I am setting the fetch mode to EXTRA_LAZY as below.
class Thread
{
// ...
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Profile\Entity\Message", mappedBy="thread", fetch="EXTRA_LAZY")
* #ORM\OrderBy({"timeSent" = "ASC"})
*/
protected $messages;
}
This allows me to use the slice method to issue a LIMIT SQL query to the database. All good so far. Because my messages are encrypted, I need to decrypt them in my service layer before handling the thread object off to the controller (and ultimately view). To accomplish this, I am doing the following in my service:
foreach ($thread->getMessages()->slice(0, 10) as $message) {
// Decrypt message
}
The call to slice triggers an SQL query that fetches 10 messages. In my view, I am doing the following to render the thread's messages:
$this->partialLoop()->setObjectKey('message');
echo $this->partialLoop('partial/thread/message.phtml', $thread->getMessages());
The problem is that this fetches the entire collection of messages from the database. If I call slice as in my service, the same SQL query with LIMIT 10 is issued to the database, which is not desirable.
How can I process a limited collection of messages in my service layer without issuing another SQL query in my view? That is, to have doctrine create a single SQL query, not two. I could simply decrypt my messages in my view, but that kind of defeats the purpose of having a service layer in this case. I could surely fetch the messages "manually" and add them to the thread object, but if I could do it automatically through the association, then that would be much preferred.
Thanks in advance!
How about a slightly different approach than most have suggested:
Slice
In the Thread entity, have a dedicated method for returning slices of messages:
class Thread
{
// ...
/**
* #param int $offset
* #param int|null $length
* #return array
*/
public function getSliceOfMessages($offset, $length = null)
{
return $this->messages->slice($offset, $length);
}
}
This would take care of easily retrieving a slice in the view, without the risk of fetching the entire collection.
Decrypting message content
Next you need the decrypted content of the messages.
I suggest you create a service that can handle encryption/decryption, and have the Message entity depend on it.
class Message
{
// ...
/**
* #var CryptService
*/
protected $crypt;
/**
* #param CryptService $crypt
*/
public function __construct(CryptService $crypt)
{
$this->crypt = $crypt;
}
}
Now you have to create Message entities by passing a CryptService to it. You can manage that in the service that creates Message entities.
But this will only take care of Message entities that you instantiate, not the ones Doctrine instantiates. For this, you can use the PostLoad event.
Create an event-listener:
class SetCryptServiceOnMessageListener
{
/**
* #var CryptService
*/
protected $crypt;
/**
* #param CryptService $crypt
*/
public function __construct(CryptService $crypt)
{
$this->crypt = $crypt;
}
/**
* #param LifecycleEventArgs $event
*/
public function postLoad(LifecycleEventArgs $event)
{
$entity = $args->getObject();
if ($entity instanceof Message) {
$message->setCryptService($this->crypt);
}
}
}
This event-listener will inject a CryptService into the Message entity whenever Doctrine loads one.
Register the event-listener in the bootstrap/configuration phase of your application:
$eventListener = new SetCryptServiceOnMessageListener($crypt);
$eventManager = $entityManager->getEventManager();
$eventManager->addEventListener(array(Events::postLoad), $eventListener);
Add the setter to the Message entity:
class Message
{
// ...
/**
* #param CryptService $crypt
*/
public function setCryptService(CryptService $crypt)
{
if ($this->crypt !== null) {
throw new \RuntimeException('We already have a crypt service, you cannot swap it.');
}
$this->crypt = $crypt;
}
}
As you can see, the setter safeguards against swapping out the CryptService (you only need to set it when none is present).
Now a Message entity will always have a CryptService as dependency, whether you or Doctrine instantiated it!
Finally we can use the CryptService to encrypt and decrypt the content:
class Message
{
// ...
/**
* #param string $content
*/
public function setContent($content)
{
$this->content = $this->crypt->encrypt($content);
}
/**
* #return string
*/
public function getContent()
{
return $this->crypt->decrypt($this->content);
}
}
Usage
In the view you can do something like this:
foreach ($thread->getSliceOfMessages(0, 10) as $message) {
echo $message->getContent();
}
As you can see this is dead simple!
Another pro is that the content can only exist in encrypted form in a Message entity. You can never accidentally store unencrypted content in the database.
The slice method's comment says:
Calling this method will only return the selected slice and NOT change the elements contained in the collection slice is called on.
So calling slice has no effect on the global PersistentCollection returned by your getMessages method: I don't think what you try to achieve here is doable.
As a workaround, you could declare a $availableMessages attribute in your Thread class, said attribute not being mapped to Doctrine. It would look like:
class Thread {
/**
* #var ArrayCollection
*/
protected $availableMessages;
...
public function __construct() {
...
$this->availableMessages = new ArrayCollection();
}
...
public function getAvailableMessages() {
return $this->availableMessages;
}
public function addAvailableMessage($m) {
$this->availableMessages->add($m);
}
...
}
When you process your messages in your service, you could:
$messages = $thread->getMessages()->slice(0, 10);
foreach ($messages as $message) {
//do your process...
...
//add the unpacked message to the proper "placeholder"
$thread->addAvailableMessage($message);
}
Then in your view:
echo $this->partialLoop(
'partial/thread/message.phtml',
$thread->getAvailableMessages()
);
There might be some differences in your implementation, like you might prefer having an ID-indexed array instead of an ArrayCollection for $availableMessages, and/or use a set instead of an add method, but you get the idea here...
Anyway, this logic allows you to control the amount of output messages from the service layer, without any implication of later-called layers, which is what you want from what I understood :)
Hope this helps!
I believe you should have a different class for decrypted message and push them separately into your view. Because Message is actually your entity-model and should be used for mapping data into your db but your purpose is quite different.
Because as I understood from your code you make smth. like:
$message->setText(decrypt($message->getText));
and after that you making even worse
$thread->setMessages(new ArrayCollection($thread->getMessages()->slice(0, 10)));
Imagine then, that after you made this changes, somewhere in the code $em->flush() happens.
You will loose every message except those 10, and they will be stored decrypted.
So you can push them as a separate array of decrypted messages (if you have only one thread on this view) or multi-dimensional array of messages with ThreadID as first level.
Or you can implement the answer Stock Overflaw gave you.
It's up to you, but you certainly should change your approach.
And by my opinion the best solution would be an implementation of decryption as ViewHelper.
It seems as if I figured it out. When calling slice, I get an array of entities. If I convert this array to an instance of \Doctrine\Common\Collections\ArrayCollection and call the setMessages method, it seems to work.
$messages = $thread->getMessages()->slice(0, 10);
foreach ($messages as $message) {
// Decrypt message
}
$thread->setMessages(new ArrayCollection($messages));
Then, in my view, I can just call $thread->getMessages() as I normally would. This has the advantage that my view does not need to know that any decryption is happening or know about any other properties. The result is that only one SQL query is executed, which is the one "generated by" slice - exactly what I wanted.
Related
I have this set of entities that we call nomenclators, which basically have an id field and a text-based field. The CRUD operations for these entities are virtually the same, just that in some of them the text field is called state while in others is area... and so on.
Given that, I created this base Controller
class NomenclatorsController extends Controller
{
use ValidatorTrait;
protected function deleteENTITYAction(Request $req, $entityName)
{
$id = $req->request->get('id');
$spService = $this->get('spam_helper');
$resp = $spService->deleteEntitySpam("AplicacionBaseBundle:$entityName", $id);
if ($resp == false)
return new JsonResponse("error.$entityName.stillreferenced", Response::HTTP_FORBIDDEN);
return new JsonResponse('', Response::HTTP_ACCEPTED);
}
protected function listENTITYAction(Request $req, $entityName)
{
$size = $req->query->get('limit');
$page = $req->query->get('page');
$spService = $this->get('spam_helper');
$objectResp = $spService->allSpam("AplicacionBaseBundle:$entityName", $size, $page);
$arrayResp = $spService->spamsToArray($objectResp);
return new JsonResponse($arrayResp, Response::HTTP_ACCEPTED);
}
protected function updateENTITYAction(Request $req, $entityName)
{
$id = $req->request->get('id');
$entity = null;
if (is_numeric($id)) {
$entity = $this->getDoctrine()->getRepository("AplicacionBaseBundle:$entityName")->find($id);
} else if (!is_numeric($id) || $id == null) {
//here comes the evil
eval('$entity=new \\AplicacionBaseBundle\\Entity\\' . $entityName . '();');
$entity->setEliminado(false);
$entity->setEmpresa($this->getUser()->getEmpresa());
}
$this->populateEntity($req->request, $entity);
$errors = $this->validate($entity);
if ($errors)
return new Response(json_encode($errors), Response::HTTP_BAD_REQUEST);
$spamService = $this->get('spam_helper');
$spamService->saveEntitySpam($entity);
}
//Override in children
protected function populateEntity($req, $entity)
{
}
}
So, each time I need to write a controller for one of these nomenclators I extend this NomenclatorsController and works like a charm.
The thing is in the updateENTITYAction I use eval for dynamic instantiation as you can see, but given all I have readed about how bad is eval I am confused now, and even when there is no user interaction in my case I want to know if there is a better way of doing this than eval and if there is any noticiable performance issue when using eval like this.
By the way I am working in a web json api with symfony and extend.js, which means no view is generated in the server,my controllers match a route and receive a sort of request params and do the work.
I've done something similar in the past. Since you are extending a base class using specific classes for each entity you can instance your entity from the controller that extends NomenclatorsController.
If one of your entities is called Foo you will have a FooController that extends NomenclatorsController. Just overwrite updateENTITYAction and pass back needed variables.
An example:
<?php
use AplicacionBaseBundle\Entity\Foo as Item;
class FooController extends NomenclatorsController
{
/**
* Displays a form to edit an existing item entity.
*
* #Route("/{id}/edit")
* #Method({"GET", "POST"})
* #Template()
* #param Request $request
* #param Item $item
* #return array|bool|\Symfony\Component\HttpFoundation\RedirectResponse
*/
public function updateENTITYAction(Request $request, Item $item)
{
return parent::updateENTITYAction($request, $item);
}
}
This way you are sending directly the entity to NomenclatorController and you don't even need to know the entityName.
Humm I'll me too advise you to avoid the eval function. It's slow and a bad practice.
What you want here is the factory pattern,
You could define a service to create the entites for you
#app/config/services.yml
app.factory.nomenclators:
class: YourNamespace\To\NomenclatorsFactory
And your factory might be like this
namespace YourNamespace\To;
use YourNamespace\To\Entity as Entites;
class NomenclatorsFactory {
// Populate this array with all your Nomenclators class names with constants OR with reflection if you have many
private $allowedNomemclators = [];
/**
* #param $entityName
* #return NomenclatorsInterface|false
*/
public function getEntity($entityName)
{
if(!is_string($entityName) || !in_array($entityName, $this->allowedNomemclators)) {
// Throw exception or exit false
return false;
}
return new $entityName;
}
}
Then you have to create the NomenclatorsInterface and define in it all the common methods between all your entities. Moreover define one more method getSomeGoodName, the job of this method is to return the good property (area or state)
With this structure your controller can only instances the Nomenclators entities and don't use anymore the eval evil method haha
Moreover you don't have to worry about about the state and area property
Ask if something isn't clear :D
I hope it help !
I'm trying to use Zend\Validator to validate objects but I find it hard for a couple of reasons and am now wondering if I'm doing something fundamentally wrong or if the component is just not a good choice to do so…
Ideally I'd like to run
$objectValidator = new ObjectValidator();
$objectValidator->isValid($object);
So in this case I would put (sub-)validators for the objects properties in ObjectValidator's isValid() method for example like this:
public function isValid($value, $context = null)
{
$propertyValidator = new Zend\Validator\Callback(function($value) {
return false;
});
if (!$propertyValidator->isValid($value)) {
foreach ($propertyValidator->getMessages() as $code => $message) {
$this->abstractOptions['messages'][$code] = $message;
}
return false;
}
return true;
}
The way to merge the messages from the property's validator I've copied from the component's EmailAddress validator that falls back on the Hostname validator.
The trouble starts when I'm using a type of validator twice (e.g. Callback) no matter if on the same property or a different since the messages are merged and I'm loosing information that I'd like to have. I could build a way to manage the messages myself but I'm wondering if there's not a better solution.
I also thought of using Zend\InputFilter instead creating Zend\Inputs for each property that I want to run checks on. This way I certainly get all the messages but it adds a rather annoying task of dismantling the object before I can validate it.
Any input highly appreciated.
I would suggest to use the ZF2 InputFilter class as a base for deep validating objects and properties.
You can implement Zend\Stdlib\ArraySerializableInterface interface to solve the "annoying task of dismantling the object" issue with a getArrayCopy method:
<?php
namespace Application\Model;
use Zend\Stdlib\ArraySerializableInterface;
class MyObject implements ArraySerializableInterface
{
/**
* Exchange internal values from provided array
*
* #param array $array
* #return void
*/
public function exchangeArray(array $array)
{
//...Your custom logic to exchange properties with data from an array
}
/**
* Return an array representation of the object
*
* #return array
*/
public function getArrayCopy()
{
//...Your custom logic to get array copy of the object for validation
}
}
Or make a custom hydrator class that does this for you...
In applying the Data Mapper pattern, the model (Domain Model in my case) is responsible for business logic where possible, rather than the mapper that saves the entity to the database.
Does it seem reasonable to build a separate business logic validator for processing user-provided data outside of the model?
An example is below, in PHP syntax.
Let's say we have an entity $person. Let's say that that entity has a property surname which can not be empty when saved.
The user has entered an illegal empty value for surname. Since the model is responsible for encapsulating business logic, I'd expect $person->surname = $surname; to somehow say that the operation was not successful when the user-entered $surname is an empty string.
It seems to me that $person should throw an exception if we attempt to fill one of its properties with an illegal value.
However, from what I've read on exceptions "A user entering 'bad' input is not an exception: it's to be expected." The implication is to not rely on exceptions to validate user data.
How would you suggest approaching this problem, with the balance between letting the Domain Model define business logic, yet not relying on exceptions being thrown by that Domain Model when filling it with user-entered data?
A Domain Model is not necessarily an object that can be directly translated to a database row.
Your Person example does fit this description, and I like to call such an object an Entity (adopted from the Doctrine 2 ORM).
But, like Martin Fowler describes, a Domain Model is any object that incorporates both behavior and data.
a strict solution
Here's a quite strict solution to the problem you describe:
Say your Person Domain Model (or Entity) must have a first name and last name, and optionally a maiden name. These must be strings, but for simplicity may contain any character.
You want to enforce that whenever such a Person exists, these prerequisites are met. The class would look like this:
class Person
{
/**
* #var string
*/
protected $firstname;
/**
* #var string
*/
protected $lastname;
/**
* #var string|null
*/
protected $maidenname;
/**
* #param string $firstname
* #param string $lastname
* #param string|null $maidenname
*/
public function __construct($firstname, $lastname, $maidenname = null)
{
$this->setFirstname($firstname);
$this->setLastname($lastname);
$this->setMaidenname($maidenname);
}
/**
* #param string $firstname
*/
public function setFirstname($firstname)
{
if (!is_string($firstname)) {
throw new InvalidArgumentException('Must be a string');
}
$this->firstname = $firstname;
}
/**
* #return string
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* #param string $lastname
*/
public function setLastname($lastname)
{
if (!is_string($lastname)) {
throw new InvalidArgumentException('Must be a string');
}
$this->lastname = $lastname;
}
/**
* #return string
*/
public function getLastname()
{
return $this->lastname;
}
/**
* #param string|null $maidenname
*/
public function setMaidenname($maidenname)
{
if (!is_string($maidenname) or !is_null($maidenname)) {
throw new InvalidArgumentException('Must be a string or null');
}
$this->maidenname = $maidenname;
}
/**
* #return string|null
*/
public function getMaidenname()
{
return $this->maidenname;
}
}
As you can see there is no way (disregarding Reflection) that you can instantiate a Person object without having the prerequisites met.
This is a good thing, because whenever you encounter a Person object, you can be a 100% sure about what kind of data you are dealing with.
Now you need a second Domain Model to handle user input, lets call it PersonForm (because it often represents a form being filled out on a website).
It has the same properties as Person, but blindly accepts any kind of data.
It will also have a list of validation rules, a method like isValid() that uses those rules to validate the data, and a method to fetch any violations.
I'll leave the definition of the class to your imagination :)
Last you need a Controller (or Service) to tie these together. Here's some pseudo-code:
class PersonController
{
/**
* #param Request $request
* #param PersonMapper $mapper
* #param ViewRenderer $view
*/
public function createAction($request, $mapper, $view)
{
if ($request->isPost()) {
$data = $request->getPostData();
$personForm = new PersonForm();
$personForm->setData($data);
if ($personForm->isValid()) {
$person = new Person(
$personForm->getFirstname(),
$personForm->getLastname(),
$personForm->getMaidenname()
);
$mapper->insert($person);
// redirect
} else {
$view->setErrors($personForm->getViolations());
$view->setData($data);
}
}
$view->render('create/add');
}
}
As you can see the PersonForm is used to intercept and validate user input. And only if that input is valid a Person is created and saved in the database.
business rules
This does mean that certain business logic will be duplicated:
In Person you'll want to enforce business rules, but it can simple throw an exception when something is off.
In PersonForm you'll have validators that apply the same rules to prevent invalid user input from reaching Person. But here those validators can be more advanced. Think off things like human error messages, breaking on the first rule, etc. You can also apply filters that change the input slightly (like lowercasing a username for example).
In other words: Person will enforce business rules on a low level, while PersonForm is more about handling user input.
more convenient
A less strict approach, but maybe more convenient:
Limit the validation done in Person to enforce required properties, and enforce the type of properties (string, int, etc). No more then that.
You can also have a list of constraints in Person. These are the business rules, but without actual validation code. So it's just a bit of configuration.
Have a Validator service that is capable of receiving data along with a list of constraints. It should be able to validate that data according to the constraints. You'll probably want a small validator class for each type of constraint. (Have a look at the Symfony 2 validator component).
PersonForm can have the Validator service injected, so it can use that service to validate the user input.
Lastly, have a PersonManager service that's responsible for any actions you want to perform on a Person (like create/update/delete, and maybe things like register/activate/etc). The PersonManager will need the PersonMapper as dependency.
When you need to create a Person, you call something like $personManager->create($userInput); That call will create a PersonForm, validate the data, create a Person (when the data is valid), and persist the Person using the PersonMapper.
The key here is this:
You could draw a circle around all these classes and call it your "Person domain" (DDD). And the interface (entry point) to that domain is the PersonManager service. Every action you want to perform on a Person must go through PersonManager.
If you stick to that in your application, you should be safe regarding to ensuring business rules :)
I think the statement "A user entering 'bad' input is not an exception: it's to be expected." is debatable...
But if you don't want to throw an exception, why don't you create an isValid(), or getValidationErrors() method?
You can then throw an exception, if someone tries to save an invalid entity to the database.
Your domain requires that when creating a person, you will provide a first name and a surname. The way I normally approach this is by validating the input model, an input model might look like;
class PersonInput
{
var $firstName;
var $surname;
public function isValid() {
return isset($this->firstName) && isset($this->surname);
}
}
This is really a guard, you can put these rules in your client side code as well to try and prevent this scenario, or you can you return from your post with an invalid person message. I don't see this as an exception, more along the lines of "to be expected" which is why I write the guard code. Your entry into your domain now might look like;
public function createPerson(PersonInput $input) {
if( $input->isValid()) {
$model->createPerson( $input->firstName, $input->surname);
return 'success';
} else {
return 'person must comtain a valid first name and surname';
}
}
This is just my opinion, and how I go about keeping my validation logic away from the domain logic.
I think your design in which the $person->surname = ''; should raise an error or exception could be simplified.
Return the error once
You dont want to catch errors all the time when assigning each value, you want a simple one-stop solution like $person->Valididate() that looks at the current values. Then when you'd call a ->Save() function, it could automatically call ->Validate() first and simply return False.
Return the error details
But returning False, or even an errorcode is often not sufficient: you want the 'who? why?' details. So lets use a class instance to contain the details, i call it ItemFieldErrors. Its passed to Save() and only looked at when Save() returns False.
public function Validate(&$itemFieldErrors = NULL, $aItem = NULL);
Try this complete ItemFieldErrors implementation. An array would suffice, but i found this more structured, versatile and self-documenting. You could always pass and parse the error details more intelligently anywhere/way you like, but often (if not always..) just outputting the asText() summary would do.
/**
* Allows a model to log absent/invalid fields for display to user.
* Can output string like "Birthdate is invalid, Surname is missing"
*
* Pass this to your Validate() model function.
*/
class ItemFieldErrors
{
const FIELDERROR_MISSING = 1;
const FIELDERROR_INVALID = 2;
protected $itemFieldErrors = array();
function __construct()
{
$this->Clear();
}
public function AddErrorMissing($fieldName)
{
$this->itemFieldErrors[] = array($fieldName, ItemFieldErrors::FIELDERROR_MISSING);
}
public function AddErrorInvalid($fieldName)
{
$this->itemFieldErrors[] = array($fieldName, ItemFieldErrors::FIELDERROR_INVALID);
}
public function ErrorCount()
{
$count = 0;
foreach ($this->itemFieldErrors as $error) {
$count++;
}
unset($error);
return $count;
}
public function Clear()
{
$this->itemFieldErrors = array();
}
/**
* Generate a human readable string to display to user.
* #return string
*/
public function AsText()
{
$s = '';
$comma = '';
foreach($this->itemFieldErrors as $error) {
switch ($error[1]) {
case ItemFieldErrors::FIELDERROR_MISSING:
$s .= $comma . sprintf(qtt("'%s' is absent"), $error[0]);
break;
case ItemFieldErrors::FIELDERROR_INVALID:
$s .= $comma . sprintf(qtt("'%s' is invalid"), $error[0]);
break;
default:
$s .= $comma . sprintf(qtt("'%s' has unforseen issue"), $error[0]);
break;
}
$comma = ', ';
}
unset($error);
return $s;
}
}
Then ofcourse there is $person->Save() that needs to receive it so it can pass it through to Validate(). In my code, whenever i 'load' data from the user (form submission) the same Validate() is called already, not only when saving.
The model, would do this:
class PersonModel extends BaseModel {
public $item = array();
public function Validate(&$itemFieldErrors = NULL, $aItem = NULL) {
// Prerequisites
if ($itemFieldErrors === NULL) { $itemFieldErrors = new ItemFieldErrors(); }
if ($aItem === NULL) { $aItem = $this->item; }
// Validate
if (trim($aItem['name'])=='') { $itemFieldErrors->AddErrorMissing('name'); }
if (trim($aItem['surname'])=='') { $itemFieldErrors->AddErrorMissing('surname'); }
if (!isValidDate($aItem['birthdate'])) { $itemFieldErrors->AddErrorInvalid('birthdate'); }
return ($itemFieldErrors->ErrorCount() == 0);
}
public function Load()..
public function Save()..
}
This simple model would hold all data in $item, so it simply exposes fields as $person->item['surname'].
So far I feel like I've understood the concept and the advantages of OOP programming, and I've not really had any difficulties with understanding how to work with classes in PHP.
However, this has left me just a little confused. I think I may understand it, but I'm still uncertain.
I've been following a set of video tutorials (not sure on the rules on linking to outside resources, but I found them on youtube), and they're pretty self explanatory. Except, frustratingly, when the tutor decided to pass one class as a parameter within another class. At least I think that's what's happening;
Class Game
{
public function __construct()
{
echo 'Game Started.<br />';
}
public function createPlayer($name)
{
$this->player= New Player($this, $name);
}
}
Class Player
{
private $_name;
public function __construct(Game $g, $name)
{
$this->_name = $name;
echo "Player {$this->_name} was created.<br />";
}
}
Then I'm instantiating an object of the Game class and calling its method;
$game = new Game();
$game-> createPlayer('new player');
Rather frustratingly, the tutor doesn't really explain why he has done this, and hasn't displayed, as far as I can see, any calls in the code that would justify it.
Is the magic method constructor in Player passing in the Game class as a reference? Does this mean that the entire class is accessible within the Player class by reference? When referencing $this without pointing to any particular method or property, are you referencing the entire class?
If this is what is happening, then why would I want to do it? If I've created a Player inside my Game Class, then surely I can just access my Player Properties and Methods within the Game Class, right? Why would I want my Game Class inside my Player class as well? Could I then, for example, call createPlayer() within the Player class?
I apologise if my explanation has been at all confusing.
I guess my question boils down to; what is it that I'm passing as a parameter exactly, and why would I want to do it in every day OOP programming?
It's called type hinting and he's not passing the entire class as a parameter but ratter hinting the Class Player about the type of the first parameter
PHP 5 introduces type hinting. Functions are now able to force parameters to be objects (by specifying the name of the class in the function prototype), interfaces, arrays (since PHP 5.1) or callable (since PHP 5.4). However, if NULL is used as the default parameter value, it will be allowed as an argument for any later call.
(Extracted from php manual)
Does this mean that the entire class is accessible within the Player class by reference?
Not the entire class, but you can access the an instance of the class you are passing as a parameter
The method DOES expect to get the an object which is an instance of Game anything else, and it will error out.
You are passing an instance of Game just here: New Player($this, $name); The key word $this refers to the object instance you are in.
And last....I (and nobody else for that matter) has any idea why the author did it, as he is not using the Game instance after he passes it.
Why would u pass a Instance of a class?
Imagine a Class that accepts Users as input and according to some logic does something with them. (No comments in code, as function name and class name are supposed to be self-explanatory)
class User{
protected $name,$emp_type,$emp_id;
protected $department;
public function __construct($name,$emp_type,$emp_id){
$this->name = $name;
$this->emp_type = $emp_type;
$this->emp_id = $emp_id;
}
public function getEmpType(){
return $this->emp_type;
}
public function setDep($dep){
$this->department = $dep;
}
}
class UserHub{
public function putUserInRightDepartment(User $MyUser){
switch($MyUser->getEmpType()){
case('tech'):
$MyUser->setDep('tech control');
break;
case('recept'):
$MyUser->setDep('clercks');
break;
default:
$MyUser->setDep('waiting HR');
break;
}
}
}
$many_users = array(
0=>new User('bobo','tech',2847),
1=>new User('baba','recept',4443), many more
}
$Hub = new UserHub;
foreach($many_users as $AUser){
$Hub->putUserInRightDepartment($AUser);
}
/**
* Game class.
*/
class Game implements Countable {
/**
* Collect players here.
*
* #var array
*/
private $players = array();
/**
* Signal Game start.
*
*/
public function __construct(){
echo 'Game Started.<br />';
}
/**
* Allow count($this) to work on the Game object.
*
* #return integer
*/
public function Count(){
return count($this->players);
}
/**
* Create a player named $name.
* $name must be a non-empty trimmed string.
*
* #param string $name
* #return Player
*/
public function CreatePlayer($name){
// Validate here too, to prevent creation if $name is not valid
if(!is_string($name) or !strlen($name = trim($name))){
trigger_error('$name must be a non-empty trimmed string.', E_USER_WARNING);
return false;
}
// Number $name is also not valid
if(is_numeric($name)){
trigger_error('$name must not be a number.', E_USER_WARNING);
return false;
}
// Check if player already exists by $name (and return it, why create a new one?)
if(isset($this->players[$name])){
trigger_error("Player named '{$Name}' already exists.", E_USER_NOTICE);
return $this->players[$name];
}
// Try to create... this should not except but it's educational
try {
return $this->players[$name] = new Player($this, $name);
} catch(Exception $Exception){
// Signal exception
trigger_error($Exception->getMessage(), E_USER_WARNING);
}
// Return explicit null here to show things went awry
return null;
}
/**
* List Players in this game.
*
* #return array
*/
public function GetPlayers(){
return $this->players;
}
/**
* List Players in this game.
*
* #return array
*/
public function GetPlayerNames(){
return array_keys($this->players);
}
} // class Game;
/**
* Player class.
*/
class Player{
/**
* Stores the Player's name.
*
* #var string
*/
private $name = null;
/**
* Stores the related Game object.
* This allows players to point to Games.
* And Games can point to Players using the Game->players array().
*
* #var Game
*/
private $game = null;
/**
* Instantiate a Player assigned to a Game bearing a $name.
* $game argument is type-hinted and PHP makes sure, at compile time, that you provide a proper object.
* This is compile time argument validation, compared to run-time validations I do in the code.
*
* #param Game $game
* #param string $name
* #return Player
*/
public function __construct(Game $game, $name){
// Prevent object creation in case $name is not a string or is empty
if(!is_string($name) or !strlen($name = trim($name))){
throw new InvalidArgumentException('$name must be a non-empty trimmed string.');
}
// Prevent object creation in case $name is a number
if(is_numeric($name)){
throw new InvalidArgumentException('$name must not be a number.');
}
// Attach internal variables that store the name and Game
$this->name = $name;
$this->game = $game;
// Signal success
echo "Player '{$this->name}' was created.<br />";
}
/**
* Allow strval($this) to return the Player name.
*
* #return string
*/
public function __toString(){
return $this->name;
}
/**
* Reference back to Game.
*
* #return Game
*/
public function GetGame(){
return $this->game;
}
/**
* Allow easy access to private variable $name.
*
* #return string
*/
public function GetName(){
return $this->name;
}
} // class Player;
// Testing (follow main comment to understand this)
$game = new Game();
$player1 = $game->CreatePlayer('player 1');
$player2 = $game->CreatePlayer('player 2');
var_dump(count($game)); // number of players
var_dump($game->GetPlayerNames()); // names of players
Rewrote your code in a nicer way and added some missing variables that made that code pointless:
In player class you don't store the game.
In game class, you support only one player.
No error checking... anywhere.
Fixed all those plus:
Added exceptions (to prevent object creation)
Try{} catch(...){} Exception handling any OOP dev should know
Implemented Countable interface to allow count($game) players
Some more tricks that will give you a good read
Follow the comments and I hope your code will make more sense after you read it.
I have one test method that depends on another method that itself uses a data provider in PHPUnit:
/**
* #dataProvider getFields
*/
public function testCanDoSomeStuff($parm1, $parm2) {
$result = my_func($parm1, $parm2);
$this->assertNotNull($result);
return $result;
}
/**
* #depends testCanDoSomeStuff
*/
public function testCanDoSomeMoreStuff($result) {
$this->assertNotNull($result);
}
I also have a getFields() data provider function, no need to show that here.
The first test that relies on the data provider passes - $result is NOT null.
I expect that the result of the test will be passed to the dependent test as the $result parameter. However, the testCanDoSomeMoreStuff function receives a NULL parameter and the test fails.
Update
This simple test case demonstrates the problem:
class MyTest extends PHPUnit_Framework_TestCase {
/**
* #dataProvider myFunc
*/
public function testCanDoSomeStuff($value) {
$this->assertNotNull($value);
return $value;
}
/**
* #depends testCanDoSomeStuff
*/
public function testCanDoSomeMoreStuff($value) {
$this->assertNotNull($value);
}
/**
* Data provider function
*/
public function myFunc() {
$values = array('22');
return array($values);
}
}
As a workaround for now, I've stored the result in a static property between tests.
The problem is the result of several factors:
Each test result is stored in an array using the test's name as the key.
The name for a test that receives data is <name> with data set #<x>.
The #depends annotation doesn't accept multiple words.
There is a hacky workaround: override TestCase::getDataSetAsString to return a name that the annotation will accept. This is made slightly problematic since the required TestCase fields are private, but with PHP 5.3.2+ you can get around that.
Important: Unfortunately, you cannot have the dependent test run for every data row--only one specific row. If your data provider returns only one row of data, this isn't an issue.
Here's the code with a sample test. Note that you don't have to name your data row. If you leave off the 'foo' key, change the #depends to testOne-0.
class DependencyTest extends PHPUnit_Framework_TestCase
{
/**
* #dataProvider data
*/
public function testOne($x, $y) {
return $x + $y;
}
public function data() {
return array(
'foo' => array(1, 2),
);
}
/**
* #depends testOne-foo
*/
public function testTwo($z) {
self::assertEquals(3, $z);
}
protected function getDataSetAsString($includeData = false) {
if (!$includeData && $this->getPrivateField('data')) {
return '-' . $this->getPrivateField('dataName');
}
return parent::getDataSetAsString($includeData);
}
private function getPrivateField($name) {
$reflector = new ReflectionProperty('PHPUnit_Framework_TestCase', $name);
$reflector->setAccessible(true);
return $reflector->getValue($this);
}
}
Obviously, this is not a long-term solution. It would be better of you could have the dependent test run once for each test result from the method receiving the data. You could submit a feature request or pull request to PHPUnit.
If your $result in testCanDoSomeStuff() is really not null, then this should work.
To take this apart, first try to simplify it without the data provider, something like this:
class StackTest extends PHPUnit_Framework_TestCase {
public function testCanDoSomeStuff() {
$result = true;
$this->assertTrue($result);
return $result;
}
/**
* #depends testCanDoSomeStuff
*/
public function testCanDoSomeMoreStuff($result) {
$this->assertNotNull($result);
}
}
Testing this should result into something like...
~>phpunit test.php
PHPUnit 3.6.11 by Sebastian Bergmann.
..
Time: 1 second, Memory: 3.25Mb
OK (2 tests, 2 assertions)
Now add the data provider, replace my simple variable with your function and then test it again.
If this result differs, var_dump the variable $result before you return it in testcase testCanDoSomeStuff(). If it isn't null there, bug the behaviour.
I also expected the problem described to work, and after some research, I found out that this is not a bug, but an expected, not documented, behavior. The dependent test does not know about the data sets returned by the provider, and that's why the test parameter is null.
Source: https://github.com/sebastianbergmann/phpunit/issues/183#issuecomment-816066
The #dataProvider annotations get computed before test execution. Basically, the pre-test phase creates a test method for every set of parameters provided by the data provider. The #depends is dependent upon what is essentially the prototype of the data driven test, so in a way the #depends is on a non-existent (not executed test).
Another way to think of it, is that if provider was supplying more than one set of parameters. PHPUnit would make that many testDataProvider methods but there would not be that many testDataReceiver methods because there is not an #dataProvider method on that test method for the pre-test phase.
You can however had #depends and #dataProvider on the same test method. Just be careful to get the parameter order right, although in this case there may not be a first parameter.
Basically, you should use data providers when the data set has multiple rows. However, you can always use #depend and #dataProvider at the same time to achieve roughly the same behavior.