Symfony2 structural composite pattern with entities - php

I am trying to implement a simple menu composite pattern.
These are the following classes i came up with.
MenuItem:
namespace MYNAME\MYBUNDLE\Entity;
use MYNAME\MYBUNDLE\Menu\MenuComponent;
class MenuItem implements MenuComponent
{
private $id;
private $name;
private $path;
private $parent;
private $visible;
private $createdOn;
private $templating;
private $attr;
private $children;
private $website;
private $position = 1;
public function __construct($name = null, $path = null, $attr = array(), $visible = true)
{
$this->name = $name;
$this->path = $path;
$this->visible = $visible;
$this->attr = $attr;
$this->createdOn = new \DateTime;
}
public function prePersist()
{
$this->createdOn = new \DateTime;
}
public function build()
{
$data['menu_item'] = $this;
$data['options'] = $this->attr;
if($this->hasChildren())
return $this->templating->render('MYBUNDLE:Menu:menu_dropdown.html.twig', $data);
if($this->isChild())
return $this->parent->getTemplating()->render('MYBUNDLE:Menu:menu_item.html.twig', $data);
return $this->templating->render('MYBUNDLE:Menu:menu_item.html.twig', $data);
}
public function __toString()
{
return $this->name;
}
public function setTemplating($templating)
{
$this->templating = $templating;
}
/**
* #return bool
*/
public function isChild()
{
return $this->hasParent();
}
/**
* #return bool
*/
public function hasParent()
{
return isset($this->parent);
}
/**
* #return bool
*/
public function hasChildren()
{
return count($this->children) > 0;
}
}
If left out the getters and setters to make it a bit shorter here.
As you can see this is the entity and it contains a build() function, however this function uses the render method which in my opinion shouldn't be in an entity.
MenuController
<?php
namespace MYNAME\MYBUNDLE\Controller;
use MYNAME\MYBUNDLE\Menu\Menu;
use MYNAME\MYBUNDLE\Entity\MenuItem;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class MenuController extends Controller
{
public function generateAction()
{
$menu = new Menu($this->get('templating'));
// load menu items
$items = $this->getDoctrine()->getRepository('MYBUNDLE:MenuItem')->findOrdered();
foreach($items as $item)
{
if(!$item->hasParent())
$menu->add($item);
}
return new Response($menu->build());
}
}
The MenuController gets called to render the menu:
{{ render(controller('MYBUNDLE:Menu:generate')) }}
I would also like this to be different since it doesn't look right. Perhaps it's better to create a twig function to render the menu?
MenuComponent:
namespace MYNAME\MYBUNDLE\Menu;
interface MenuComponent {
public function build();
}
Menu:
namespace MYNAME\MYBUNDLE\Menu;
class Menu implements MenuComponent
{
private $children;
private $templating;
public function __construct($templating)
{
$this->templating = $templating;
}
public function add(MenuComponent $component)
{
$component->setTemplating($this->templating);
$this->children[] = $component;
}
public function build()
{
return $this->templating->render('MYBUNDLE:Menu:menu.html.twig', array("menu_items" => $this->children));
}
}
Menu Contains the MenuComponents and will render the menu first, in each MenuItem it's build() method is called.
I think it's better to remove the rendering logic from my MenuItem entity and place this somewhere else, however i can't figure out on how to do this properly within this design pattern.
Any help or suggestion is appreciated.

Related

change parameter value of dependent method in phpunit

i wrote a sample test case for collection like class but weird thing about this is in my testAdd method that i add a item in CustomCollectionService and it changed my parameter too. how can this happend?
class CustomCollectionService
{
/**
* #var Collection $collection
*/
public $collection;
public function makeCollection($arr)
{
$this->collection = collect($arr);
}
/**
* #param Collection $collection
*/
public function setCollection(Collection $collection): void
{
$this->collection = $collection;
}
/**
* #return mixed
*/
public function getCollection()
{
return $this->collection;
}
public function add($item)
{
return $this->collection->add($item);
}
}
and this is my test:
class CustomCollectionTest extends TestCase
{
public $collectionService;
public $collection;
protected function setUp(): void
{
$this->collectionService = new CustomCollectionService();
}
public function testCollectionCreator()
{
$arr = ['sina','1',5];
$this->assertIsArray($arr);
return $arr;
}
/**
* #param $arr
* #depends testCollectionCreator
*/
public function testAction($arr)
{
$this->collectionService->makeCollection($arr);
$this->assertIsArray($this->collectionService->getCollection()->toArray());
return $this->collectionService->getCollection();
}
/**
* #depends testAction
*/
public function testAdd($col)
{
$actualCount = $col->count();
$this->collectionService->setCollection($col);
$manipulatedCollection = $this->collectionService->add(['xx']);
dump($actualCount); // 3
dump($col->count()); //4
$this->assertEquals($actualCount+1, $manipulatedCollection->count());
}
}
Because it is an object. So when you pass the $col object to the CollectionService and call the add method within the CollectionService, it is still the $col object from your test method that is being used.

PHPUnit: How do you Unit test for multiple if-else/factory?

I have a class ParentIdResolver which returns the parent id of the product based on the type.
The class looks like:
<?php
namespace App\Model;
use App\Model\Product\Bundle;
use App\Model\Product\Configurable;
use App\Model\Product\Downloadable;
class ParentIdResolver
{
/**
* #var Bundle
*/
private $bundle;
/**
* #var Configurable
*/
private $configurable;
/**
* #var Downloadable
*/
private $downloadable;
public function __construct(
Bundle $bundle,
Configurable $configurable,
Downloadable $downloadable
) {
$this->bundle = $bundle;
$this->configurable = $configurable;
$this->downloadable = $downloadable;
}
public function getParentId($productId, $productType)
{
$parentIds = [];
if ($productType == 'bundle') {
$parentIds = $this->bundle->getParentIdsByChild($productId);
} elseif ($productType == 'configurable') {
$parentIds = $this->configurable->getParentIdsByChild($productId);
} elseif ($productType == 'downloadable') {
$parentIds = $this->downloadable->getParentIdsByChild($productId);
}
return $parentIds[0] ?? null;
}
}
And I am trying to test the getParentId() as:
<?php
namespace App\Test\Unit;
use PHPUnit\Framework\TestCase;
use App\Model\ParentIdResolver;
use App\Model\Product\Bundle;
use App\Model\Product\Configurable;
use App\Model\Product\Downloadable;
class ParentIdResolverTest extends TestCase
{
protected $model;
protected $bundleMock;
protected $configurableMock;
protected $downloadableMock;
public function setUp(): void
{
$this->bundleMock = $this->createPartialMock(
Bundle::class,
['getParentIdsByChild']
);
$this->configurableMock = $this->createPartialMock(
Configurable::class,
['getParentIdsByChild']
);
$this->downloadableMock = $this->createPartialMock(
Downloadable::class,
['getParentIdsByChild']
);
$this->model = new ParentIdResolver(
$this->bundleMock,
$this->configurableMock,
$this->downloadableMock
);
}
/**
* #dataProvider getParentIdDataProvider
*/
public function testGetParentId($productId, $productType, $parentId)
{
if ($productType == 'bundle') {
$this->bundleMock->expects($this->any())
->method('getParentIdsByChild')
->willReturn([$parentId]);
}
if ($productType == 'configurable') {
$this->configurableMock->expects($this->any())
->method('getParentIdsByChild')
->willReturn([$parentId]);
}
if ($productType == 'downloadable') {
$this->downloadableMock->expects($this->any())
->method('getParentIdsByChild')
->willReturn([$parentId]);
}
$this->assertEquals($parentId, $this->model->getParentId($productId, $productType));
}
public function getParentIdDataProvider()
{
return [
[1, 'bundle', 11],
[2, 'configurable', 22],
[3, 'downloadable', 33],
];
}
}
And I don't feel that I am doing it correctly, maybe I need to refactor the main class?
Please suggest how would you refactor or write unit test in this case.
Personally I would consider moving the responsibility for resolving the right class towards each class itself. Some would call this "ask, don't tell". It'll look something like this
<?php
namespace App\Model;
use App\Model\Product\ResolvesParentId;
class ParentIdResolver
{
/** #var ResolvesParentId[] */
private $parentIdResolvers;
public function __construct(array $parentIdResolvers)
{
$this->parentIdResolvers = $parentIdResolvers;
}
public function getParentId(int $productId, string $productType): int
{
foreach ($this->parentIdResolvers as $parentIdResolver) {
if ($parentIdResolver->supports($productType)) {
return $parentIdResolver->getParentId($productId)[0] ?? null;
}
}
return null;
}
}
<?php
namespace App\Model\Product;
interface ResolvesParentId
{
public function supports(string $productType): bool;
public function getParentIdsByChild(int $productId): array;
}
<?php
namespace App\Model\Product;
class Bundle implements ResolvesParentId
{
public function supports(string $productType): bool
{
return $productType === 'bundle';
}
public function getParentIdsByChild(int $productId): array
{
// Your implementation here.
}
}
<?php
namespace App\Model\Product;
class Configurable implements ResolvesParentId
{
public function supports(string $productType): bool
{
return $productType === 'configurable';
}
public function getParentIdsByChild(int $productId): array
{
// Your implementation here.
}
}
<?php
namespace App\Model\Product;
class Downloadable implements ResolvesParentId
{
public function supports(string $productType): bool
{
return $productType === 'downloadable';
}
public function getParentIdsByChild(int $productId): array
{
// Your implementation here.
}
}
Some consider this overkill, which depends purely on the situation you're in. Do you expect the if/else to grow in the future? Then this solution might be for you.

PhpStorm dynamic type hinting class property

I'm using a builder pattern to create an object, call methods, and return back to the calling object.
class Caller{
public function invoke() {
new Builder($this)->setColor('red')->setSize(5)-> // need typehint for Caller to return here
}
public function next() {
}
}
class Builder{
/**
* #var ????
*/
private $caller;
/**
* #param ???? $caller
*/
public function __construct($caller) {
$this->caller = $caller;
}
public function setColor($color) {
$this->color = $color;
return $this;
}
/**
* #param int $size
* #return ????
*/
public function setSize($size) {
$this->size = $size;
return $this->caller;
}
}
There is a way to invoke a parent method and return the child typehints by specifying $this as the return type in the parent class, but how can I reference a separate class type stored in $this->caller as the return type and get the typehints?

Syntax folder repository for the function edit()

I have 2 foreign keys which are fk_author and fk_bookcase , I am trying to create my function edit() via a folder Repositorie but I am stuck on the syntax again.
Here is my code via the file BookRepository
public function edit($id)
{
$books = Book::find($id);
$authors = Author::all();
$bookcases = Bookcase::all();
return Book::find($id);
}
Then, in my Controller I have this...
public function edit($id)
{
$books = $this->books->edit($id);
return view('admin.books.edit', compact('books', 'authors', 'bookcases'));
}
Do you have an idea of the problem?
Regards
If you want to retrieve the book with the related 'author' and 'bookcase', you must have defined the relations in the models. For ex:
Book Model
public function author()
{
return $this->belongsTo(Author::class, 'fk_author'); // change fk_author for the key you are using
}
public function bookcase()
{
return $this->belongsTo(Bookcase::class, 'fk_bookcase');
}
Author Model
public function books()
{
return $this->hasMany(Book::class);
}
Bookcase Model
public function books()
{
return $this->hasMany(Book::class);
}
And you doesn't need and edit() function in your repository, just a detail() (or the name what you want) which retrive the Book Object with the relations.
BookRepository
public function detail($id)
{
return Book::with([
'author',
'bookcase',
])
->find($id);
}
Then, in the Controller, yes, you have an edit function which get the detail from the repository and return the object to the edit view.
/**
* #var BookRepository
*/
private $books;
public function __construct(BookRepository $books)
{
$this->books = $books;
}
public function edit($id)
{
$book = $this->books->detail($id);
return view('admin.books.edit', compact('book'));
}
If in any case you want to also return all the authors and bookcases, I think it is better to make a repository for each one, so you can also use them from other Controllers or Classes.
AuthorRepository
public function getAll()
{
return Author::all();
}
BookcaseRepository
public function getAll()
{
return Bookcase::all();
}
Then, in the Controller
/**
* #var BookRepository
*/
private $books;
/**
* #var AuthorRepository
*/
private $authors;
/**
* #var BookcaseRepository
*/
private $bookcases;
public function __construct(BookRepository $books, AuthorRepository $authors, BookcaseRepository $bookcases)
{
$this->books = $books;
$this->authors = $authors;
$this->bookscases = $bookcases;
}
public function edit($id)
{
$book = $this->books->detail($id);
$authors = $this->authors->getAll();
$bookcases = $this->bookcases->getAll();
return view('admin.books.edit', compact('book', 'authors', 'bookcases'));
}

Php, is it OK to use traits for DI?

consider this example:
class MyClass
{
public function doSomething()
{
$this->injected->getIt();
}
}
so far so simple (apart from injected is not injected). So, in full version:
class MyClass
{
/**
* #var Injected
*/
private $injected;
public function __constructor(Injected $injected)
{
$this->injected = $injected;
}
public function doSomething()
{
$this->injected->getIt();
}
}
but I find it enoromous. Why to pollute my class with tons of code of DI? Lets split this into two entities:
trait MyClassTrait
{
/**
* #var Injected
*/
private $injected;
public function __constructor(Injected $injected)
{
$this->injected = $injected;
}
}
class MyClass
{
use MyClassTrait;
public function doSomething()
{
$this->injected->getIt();
}
}
its much nicer although I never seen anybody using it like this. Is it a good approach?
For example like this:
<?php
class Factory
{
private $services = [];
public function __construct() {
$this->services[self::class] = $this;
}
public function getByType($type){
if(isset($services[$type])){
return $services[$type];
}
if(class_exists($type)){
$reflection = new ReflectionClass($type);
$constructor = $reflection->getConstructor();
$parameters = [];
if($constructor)
foreach($constructor->getParameters() as $parameter){
if($parameter->getClass()) {
$parameters[] = $this->getByType($parameter->getClass()->name);
} else if($parameter->isDefaultValueAvailable()){
$parameters[] = $parameter->getDefaultValue();
}
}
return $services[$type] = $reflection->newInstanceArgs($parameters);
} // else throw Exception...
}
}
abstract class DI
{
public function __construct(Factory $factory) {
$reflection = new ReflectionClass(get_class($this));
foreach($reflection->getProperties() as $property){
preg_match('/#var ([^ ]+) #inject/', $property->getDocComment(), $annotation);
if($annotation){
$className = $annotation[1];
if(class_exists($className)){
$property->setAccessible(true);
$property->setValue($this, $factory->getByType($className));
} // else throw Exception...
}
}
}
}
class Injected
{
public function getIt($string){
echo $string.'<br />';
}
}
class DIByConstructor
{
/** #var Injected */
private $byConstructor;
public function __construct(Injected $injected) {
$this->byConstructor = $injected;
}
public function doSomething()
{
echo 'Class: '.self::class.'<br />';
$this->byConstructor->getIt('By Constructor');
echo '<br />';
}
}
class DIByAnnotation extends DI
{
/** #var Injected #inject */
private $byAnnotation;
public function doSomething()
{
echo 'Class: '.self::class.'<br />';
$this->byAnnotation->getIt('By Annotation');
echo '<br />';
}
}
class DIBothMethods extends DI
{
/** #var Injected */
private $byConstructor;
/** #var Injected #inject */
private $byAnnotation;
public function __construct(Factory $factory, Injected $injected) {
parent::__construct($factory);
$this->byConstructor = $injected;
}
public function doSomething()
{
echo 'Class: '.self::class.'<br />';
$this->byConstructor->getIt('By Constructor');
$this->byAnnotation->getIt('By Annotation');
echo '<br />';
}
}
$factory = new Factory();
$DIByConstructor = $factory->getByType('DIByConstructor');
$DIByConstructor->doSomething();
$DIByAnnotation = $factory->getByType('DIByAnnotation');
$DIByAnnotation->doSomething();
$DIBothMethods = $factory->getByType('DIBothMethods');
$DIBothMethods->doSomething();
Note that with #Kazz approaching (DI by Annotations) you cannot reference an Interface, instead you are referencing a Class. So this is good for fast instantiating with almost zero verbose but at the end, you are loosing all the DI potential.

Categories