I am finding a lot of the time I pass an array of custom objects to a function for processing. In my function I check to make sure it is the right class before accessing internal properties/methods.
Because I am taking an array and as far as I am aware PHP doesn't have any typed generic list I cannot use type hinting so intellisense in my IDE doesn't work and code inspection throws warnings.
I came across an old post that gave the idea of throwing in this line of code in order to get intellisense working while developing:
if (false) $myObj = new MyObject();
So I end up with a function that looks like:
function ProcessObjectArray(array $arrayOfObject)
{
foreach ($arrayOfObject as $key => $myObj) {
if (get_class($myObj) == 'MyNamespace\MyObject') {
if (false) $myObj = new MyObject();
// I can now access intellisense for MyObject in here
} else {
trigger_error('is not right object');
}
}
}
It seems like a bit of a weird hack so I am wondering if this is a sign that I am not handling arrays of objects in the best way or if there is a better way to get my intellisense working. I had a look at the arrayobject interface to see if I could create a class implementing arrayobject that would hold a typed list. While it makes it nice to put all validation inside its constructor or append like functions I couldn't get it working with intellisense as the internal container is still just a standard array. Also using get_class doesn't seem good as if a class name or namespace is renamed then the IDE refactoring features do not pick this reference up (well, PHPStorm doesn't at least and it is one I am using).
I of course don't need intellisense but the weirdness of this hack made me wonder if I have the right approach to OOP in PHP and thought I might be missing something. Is this the right way of doing it or have I missed something?
with phpstorm when you add your annotations it will correctly do the code completion for you.
/**
* #param MyNamespace\MyObject[] $arrayOfObject
*/
function ProcessObjectArray(array $arrayOfObject)
{
foreach ($arrayOfObject as $key => $myObj) {
if (get_class($myObj) == 'MyNamespace\MyObject') {
if (false) $myObj = new MyObject();
// I can now access intellisense for MyObject in here
} else {
trigger_error('is not right object');
}
}
}
However intellisense won't stop you from passing in incorrect objects, it will only help you while coding to prevent passing in the incorrect objects.
Perhaps you may wish to consider using collections instead of using generic arrays.
There are lots of examples out there, however here is a simple one which you can expand upon
class Collection
{
/** #var array */
private $items = [];
/**
* #param MyNamespace\MyObject $obj
* #return $this
*/
public function addItem(MyNamespace\MyObject $obj) {
$this->items[] = $obj;
return $this;
}
/**
* #param $index
* #return $this
*/
public function deleteItem($index) {
unset($this->items[$index]);
return $this;
}
/**
* #param $index
* #return MyNamespace\MyObject|null
*/
public function getItem($index) {
return array_key_exists($index, $this->items) ? $this->items[$index] : null;
}
/**
* #return MyNamespace\MyObject[]
*/
public function getItems() {
return $this->items;
}
/**
* #return int
*/
public function count() {
return sizeOf($this->items);
}
}
here is an example how it all works:
$collection = new \Collection;
$obj = new MyNamespace\MyObject;
$collection->addItem($obj);
foreach($collection->getItems() as $item) {
// this will already know that $item is MyNamespace\MyObject because of the annotation on the getItems() method
$item->doSomething();
}
class MyCollection implements \Iterator
{
/**
* #var CollectionElement[]
*/
private $elements = [];
public function addElement(CollectionElement $element)
{
$this->elements[] = $element;
}
/**
* #return CollectionElement
*/
public function current()
{
return current($this->elements);
}
//... Rest of the Iterator implementation
}
This way you cannot add to the collection anything other than CollectionElement. And after:
foreach($collection as $element){
/** #var CollectionElement $element */
$element->//here you will have autocompletion
}
You don't need to check anything since your collection will never contain anything you are not expecting.
If you are going this route, I would avoid using the are native arrays. I would make classes that contain arrays of a certain type. That way, you can validate the array contents in the constructor of the class, and then you can use actual type hinting for the objects, which PHP allows.
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've just started using PHPSpec and I'm really enjoying it over PHPUnit, especially the no-effort mocks and stubs. Anyway, the method I'm trying to test expects an array of Cell objects. How can I tell PHPSpec to give me an array of mocks?
Simplified version of my class
<?php
namespace Mything;
class Row
{
/** #var Cell[] */
protected $cells;
/**
* #param Cell[] $cells
*/
public function __construct(array $cells)
{
$this->setCells($cells);
}
/**
* #param Cell[] $cells
* #return Row
*/
public function setCells(array $cells)
{
// validate that $cells only contains instances of Cell
$this->cells = $cells;
return $this;
}
}
Simplified version of my test
<?php
namespace spec\MyThing\Row;
use MyThing\Cell;
use PhpSpec\ObjectBehavior;
class RowSpec extends ObjectBehavior
{
function let()
{
// need to get an array of Cell objects
$this->beConstructedWith($cells);
}
function it_is_initializable()
{
$this->shouldHaveType('MyThing\Row');
}
// ...
}
I had hoped I could do the following, but it then complains that it can't find Cell[]. Using the FQN it complains about not being able to find \MyThing\Cell[].
/**
* #param Cell[] $cells
*/
function let($cells)
{
// need to get an array of Cell objects
$this->beConstructedWith($cells);
}
The only options I can work out is to pass multiple type-hinted Cell arguments and manually combine them into an array. Am I missing something simple?
Edit: I'm using PHPSpec 2.5.3 as, unfortunately the server is currently stuck at PHP 5.3 :-(
Why don't you do something like
use Prophecy\Prophet;
use Cell; // adapt it with PSR-4 and make it use correct class
class RowSpec extends ObjectBehavior
{
private $prophet;
private $cells = [];
function let()
{
$this->prophet = new Prophet();
for ($i = 0; $i < 10; $i++) {
$this->cells[] = $this->prophet->prophesize(Cell::class);
}
$this->beConstructedWith($cells);
}
// ....
function letGo()
{
$this->prophet->checkPredictions();
}
public function it_is_a_dummy_spec_method()
{
// use here your cells mocks with $this->cells
// and make predictions on them
}
}
Explanation
In let function you instantiate a Prophet object that is, basically a mocking library/framework that is used in tandem with PHPSpec (that itself use Prophecy).
I suggest to keep the instance ($this->prophet) as will be useful for next steps.
Now, you have to create your mocks, and you can do with prophet and prophesize.
Even for the mocks, I suggest to keep them into a private variable that you probably use for predictions in your methods.
letGo function is here to check explicitly the expectations you have made on cells: without, cells are only stubs or dummies.
Of course it's handy to pass through method signature a mock and to skip checkPredictions explicitly but, as soon as you need an array of mocks, I suppose that this is the only way to reach your goal.
I have a class to manipulate objects with defined interface
class TaskManager
{
/**
* #param TaskInterface $task
* #param string $command
* #return TaskInterface
*/
public static function editTask($task, $command)
{
$task->setStatus(TaskInterface::TASK_STATUS_ACTIVE);
$task->setCommand($command);
$task->taskSave();
return $task;
}
}
I can create single object by passing its instance as an method argument. This is pretty straightforward. But how should I create many of them?
public static function export()
{
$commands = self::getCommandsToAdd();
foreach($commands as $c){
//This is wrong.
$task = new TaskInterface();
$task->setCommand($c);
$task->save();
//don't need to return it if it's saved
}
}
I can't create it this way. And it's obviously a bad idea to pass array of new objects. Another way is to pass a class name as a string and call its method to retrieve new object. But it seems wrong as well
I think I figured out a solution by myself. Can use a factory interface to pass a factory object.
interface TaskFactoryInterface
{
public static function createNew();
}
/**
* #param TaskFactoryInterface $task_factory
*/
public static function export($task_factory)
{
$commands = self::getCommandsToAdd();
foreach($commands as $c){
$task = $task_factory::createNew();
$task->setCommand($c);
$task->save();
//don't need to return it if it's saved
}
}
What do you think?
I have a class:
class Test
{
public $AppCount;
public $Apps;
// When $AppCount is accessed I want to return count( $this->Apps )
}
When I access property $AppCount, I want to return count( $this->Apps ).
Rather than having to declare an exposing function for this property and making it private, can I use a getter function like C# and Java have?
Obviously the __get is not what i want in this case as the property does already exist.
For the comments
I have this and it does not run the function when i try and access the property:
class ProjectSettingsViewModel
{
public $ProjectAppCount = 0;
public $ProjectApps = array();
public function __get( $property )
{
switch( $property )
{
case "ProjectAppCount":
return count( $this->ProjectApps );
break;
}
}
}
If the code seems okay, it must be something else going wrong.
Unfortunately, PHP does not have the getter and setter syntax you are referring to. It has been proposed, but it didn't make it into PHP (yet).
First of all, __get() is only executed if you are trying to access a property that can't be accessed (because it is protected or private, or because it does not exist). If you are calling a property that is public, __get() will never be executed.
However, I would not suggest using PHP's magic getters and setters unless you really have no other choice. They are slower, tend to become a long if/elseif/else mess very quickly, and code completion (using a smart IDE) will not work. Normal methods are a lot simpler and easier to understand. You should also read this answer.
I have used the __get() and __set() methods for a while (instead of manually created getFoo() and setFoo() methods), because it seemed like a good idea, but I've changed my mind after a short time.
So, in your case, I recommend writing normal methods:
<?php
class Test
{
/**
* #var App[]
*/
private $apps;
/**
* Returns an array containing all apps
*
* #return App[]
*/
public function getApps()
{
return $this->apps;
}
/**
* Returns the number of apps
*
* #return integer
*/
public function getAppsCount() //or call it countApps()
{
return count($this->apps);
}
}
Untested.. Should work though. Using more magic methodology.
class ProjectSettingsViewModel
{
protected $ProjectAppCount = 0;
public $ProjectApps = array();
public function __get( $property )
{
switch( $property )
{
case "ProjectAppCount":
return count( $this->ProjectApps );
break;
}
}
}
I'm just beginning with PHPUnit and TDD.
Among others, I can't really answer to this question: Is this a good test? Am i actually testing my code or something already tested (i.e. the framework or PHP itself)?
Little example, this is the test subject:
class DateMax extends Constraint
{
/**
* #var string
*/
public $limit;
/**
* #var string
*/
private $invalidLimit = 'Option "limit" should be a valid date/time string.';
public function __construct($options = null)
{
parent::__construct($options);
if(false === strtotime($this->limit)) {
throw new InvalidOptionsException($this->invalidLimit, ['limit']);
}
}
}
I want to test that InvalidOptionsException is expected when invalid "limit" options are passed, otherwise $constraint->limit holds the correct value:
/**
* #dataProvider getInvalidLimits
* #expectedException InvalidOptionsException
*/
public function testInvalidLimits($testLimit)
{
new DateMax($testLimit);
}
/**
* #dataProvider getValidLimits
*/
public function testValidLimits($testLimit)
{
$constraint = new DateMax($testLimit);
$this->assertEquals($testLimit, $constraint->limit);
}
/**
* #return array[]
*/
public function getInvalidLimits()
{
return array(array('invalid specification'), array('tomorr'));
}
/**
* #return array[]
*/
public function getValidLimits()
{
return array(array('now'), array('+1 day'),array('last Monday'));
}
So question is does this make any sense or I'm testing the framework/PHP itself?
Of course it has sense, because you override constructor of Constraint class and there is possibility that you'll break something inside it. So basing on your constructor logic basically you want to test two things:
check if you call parent's constructor with the same options, exactly once (you can use mock for this purpose, you don't care about setting appropriate limit value, because this should be tested in Constraint class)
check if an appropriate exception has been thrown when limit has wrong value (eg. null)
edit: Some use case where first test will be useful may be this one:
Let say at some moment you want to extend your DateMax constructor in this way:
public function __construct($options = null)
{
$this->optionsWithDecrementedValues = $this->doWeirdThings($options);
parent::__construct($options);
if(false === strtotime($this->limit)) {
throw new InvalidOptionsException($this->invalidLimit, ['limit']);
}
}
but for example you didn't notice that method "doWeirdThings" takes a reference as argument. So in fact it changes $options value, what you didn't expect, but first test fails so you won't miss it.