DBAL in Doctrine has implementation of Visitor pattern.
There are an interface with a few accept-methods.
interface Visitor
{
/**
* #return void
*/
public function acceptSchema(Schema $schema);
/**
* #return void
*/
public function acceptTable(Table $table);
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
{
}
}
And some classes like
class Schema
{
/**
* #return void
*/
public function visit(Visitor $visitor)
{
$visitor->acceptSchema($this);
if ($visitor instanceof NamespaceVisitor) {
foreach ($this->namespaces as $namespace) {
$visitor->acceptNamespace($namespace);
}
}
foreach ($this->_tables as $table) {
$table->visit($visitor);
}
foreach ($this->_sequences as $sequence) {
$sequence->visit($visitor);
}
}
}
Why method Schema::visit has parameter named $visitor ?
All definitions of Visitor tells that exactly Visitor should have method visit. So accept method should be inside of Visitable
So is there are some naming issues with Schema::visit($visitor)? Is DBAL\Visitor in fact Visitable? If this is true, why Visitor::accept does not call $schema->visit($this) ?
Some implementation of Dbal\Visitor looks like
/**
* {#inheritdoc}
*/
public function acceptSequence(Sequence $sequence)
{
$this->sequences->attach($sequence);
}
Is it not necessary to call $visitor->visit($this) inside of Visitable::accept()?
Related
I cannot seem to apply filtering on all children defined in a tree model format with eager loading mechanism
Here is my model definition (works great):
class Section extends Model
{
[...]
/**
* #return HasOne
*/
public function parent()
{
return $this->hasOne(
self::class,
'Id',
'IdParent'
)->with('parent');
}
/**
* #return HasMany
*/
public function children()
{
return $this->hasMany(
self::class,
'IdParent',
'Id'
)->with('children');
}
[...]
}
Now I want to filter out recursive based on a 'criteria object'
public function getMachines(SectionCriteria $sectionCriteria = NULL)
{
/**
* #var $builder Builder|Section
*/
$builder = Section::with([
'children' => function ($query) use ($sectionCriteria) {
if ($sectionCriteria) {
foreach ($sectionCriteria->getFilterFlags() as $flagName => $flagValue) {
if ($flagValue) {
$query->whereFlag($flagName); //Custom implementation
} else {
$query->whereNotFlag($flagName); //Custom implementation
}
}
}
}
]);
This works bot it is applied to the first level of the tree.
My question would be: Is there a way to pass an object to the children() relation so I can apply filters recursive (which would apply to all levels)?
Something like, let's say:
P.S: This is not possible since only a callback is accepted as a parameter
public function children($parameters)
{
return $this->hasMany(
self::class,
'IdParent',
'Id'
)->with('children'=>$parameters);
}
What I wouldn't want to use (with respect to SOLID principles):
Make a static class variable which holds criteria
A global variable of any kind
I also tried to retrieve children recursive (and apply filters) but ended up with more queries so Eloquent is preety well optimized sooo...
I used technique #1 (Make a static class variable) though I do not really like it but it works.
Model
/**
* #var null|SectionCriteria
*/
public static $childrenFilter = NULL; //This can be whatever you need since it's static
/**
* #return HasMany
*/
public function children()
{
return $this->hasMany(
self::class,
'IdParent',
'Id'
)->with(['children'=>self::searchChild()]);
}
/**
* #return \Closure
*/
public function searchChild()
{
return function ($builder) {
if (Section::$childrenFilter) {
foreach ($sectionCriteria->getFilterFlags() as $flagName => $flagValue) {
if ($flagValue) {
$query->whereFlag($flagName); //Custom implementation
} else {
$query->whereNotFlag($flagName); //Custom implementation
}
}
}
};
}
/**
* #param SectionCriteria $criteria
*/
public static function setChildSearch(SectionCriteria $criteria)
{
Section::$childrenFilter = $criteria;
}
/**
* Remove the search criteria filter
*/
public static function clearChildSearch()
{
Section::$childrenFilter = NULL;
}
Repository (the actual usage)
/**
* #param SectionCriteria|NULL $sectionCriteria
* #return Section[]|Collection
*/
public function getMachines(SectionCriteria $sectionCriteria = NULL)
{
/**
* #var $builder Builder|Section
*/
$builder = Section::with(['children']); //Here I do not need the root elements to be filtered, If needed then use: Section::with(['children'=>Section::searchChild()])
Section::setChildSearch($sectionCriteria);
$builder->orderBy('Name');
$results = $builder->get();
Section::clearChildSearch();
return $results;
}
Again...not preety but it gets the job done
New: Another way (will test this out) would be to extend the Builder class
I have a global container class:
final class Container
{
/**
* #return ForumThread
*/
public static function getForumThread()
{
if (self::$obj1 === null)
{
self::$obj1 = new ForumThread();
}
return self::$obj1;
}
/**
* #return ForumPosts
*/
public static function getForumPosts()
{
if (self::$obj2 === null)
{
self::$obj2 = new ForumPosts();
}
return self::$obj2;
}
}
my models:
class ForumThread
{
/**
* #return bool
*/
public function findBadLanguage ($inWhat)
{
return (bool)rand(0,1);
}
/**
* #return
*/
public function add ($threadName)
{
if (!$this->findBadLanguage ($threadName))
{
INSERT INTO
}
}
}
class ForumPost
{
/**
* #return
*/
public function post ($toThreadId, $comment)
{
// im talking about this:
Container::getForumThread()->findBadLanguage($comment);
}
}
I know findBadLanguage() should be in another class, but lets suppose thats okay. Lets focus on Container::get****() calls. Is it OK to turn to a global container and get objects from it? Doesnt it hury Demeter's law? (those object must be exists only once, and can be DI-ed)
EDIT: you can regard to the Container as a Factory
I have a class that controls an external API that I use in several projects (simplified example):
class PaymentModule
{
public function doPayment($customer_name, $currency, $language)
{
curl_setopt(POSTFIELDS, array($customer_name, $currence, $language));
curl_exec();
}
}
Now in a specific project I would like to "wrap" it and provide sensible defaults for a lot of parameters that I don't use here.
So I thought, I will just extend this class, have my IDE override all methods and then remove the parameters that I don't use, like this:
class MyPaymentModule extends PaymentModule
{
public function doPayment($customer_name)
{
$language = get_current_language();
parent::doPayment($customer_name, 'EUR', $language);
}
}
As I now learned (thanks to PHP strict standards), this violates the Liskov substitution principle, i.e. in OOP in general MyPaymentModule is expected to have the same interface as PaymentModule which in turn means MyPaymentModule::doPayment() is expected to have the same parameters as PaymentModule::doPayment().
I think it's not too uncommon that you want to create a class that provides sensible defaults to another one, so is there any common pattern to use here?
Of course I could go for two completely independent classes, but I would prefer a solution that still hints at the relationship between the two classes... after all they will always have the same methods, just one with less parameters.
Thats not possible with your current class design.
I would suggest to create a new class PaymentOptions which handles currency and language.
Passing the options to doPayment either with a new method setPaymentOptions or as an optional parameter for doPayment.
Something like this should work
class PaymentModule {
/**
* #var PaymentOptions
*/
private $paymentOptions = null;
function doPayment($customer_name, PaymentOptions $options = null) {
if ($options == null) {
$options = $this->paymentOptions;
}
if ($options == null) {
throw new Exception('...');
}
//...
}
/**
* #return PaymentOptions
*/
public function getPaymentOptions()
{
return $this->paymentOptions;
}
/**
* #param PaymentOptions $paymentOptions
*/
public function setPaymentOptions($paymentOptions)
{
$this->paymentOptions = $paymentOptions;
}
}
class MyPaymentModule extends PaymentModule {
function doPayment($customer_name, PaymentOptions $options = null)
{
//...
}
}
class PaymentOptions {
private $currency;
private $language;
/**
* #return mixed
*/
public function getCurrency()
{
return $this->currency;
}
/**
* #param mixed $currency
*/
public function setCurrency($currency)
{
$this->currency = $currency;
}
/**
* #return mixed
*/
public function getLanguage()
{
return $this->language;
}
/**
* #param mixed $language
*/
public function setLanguage($language)
{
$this->language = $language;
}
}
Here is my sample code which is what I want to get, but in User class I get no codecompletion (for $this->app) in PHPSotrm.
How to change that code to enable code completion? I want to avoid global.
namespace myApp
class app
{
public $pdo = "my PDO";
public function __construct() {
$this->user=new User($this);
$this->test=new Test($this);
echo $this->user->getPDO();
//"my PDO"
echo $this->test->getUserPDO();
//"my PDO"
}
}
class User
{
private $app=null;
public function __construct($app) {
$this->app=$app;
}
public function getPDO() {
return $this->app->pdo;
//no code completion
}
}
class Test
{
private $app=null;
public function __construct($app) {
$this->app=$app;
}
public function getUserPDO() {
return $this->app->user->getPDO;
//no code completion
}
}
There's plenty of information on how to achieve this. All you need to do is to add the PHPDoc describing the property type.
class User
{
/**
* #var App
*/
private $app=null;
/**
* #param App $app
*/
public function __construct($app) {
$this->app = $app;
}
/**
* #return PDO
*/
public function getPDO() {
return $this->app->pdo;
}
}
If properties are implemented via magic getters / setters, you can use the same principles on the class itself.
/**
* #property App $app
* #method Pdo getPdo()
*/
class User
{
// …
This question already has answers here:
Override method parameter with child interface as a new parameter
(2 answers)
Closed 8 years ago.
I try to use a subclass in a method with typesafe parameter. Don't know if I'm missing something or if the solution is to just don't make the parameter typesafe. What is the cleanest design option for this situation?
Look at following code:
<?php
class MyObject {
public $name;
}
class MySubObject extends MyObject {
public $subProperty;
}
abstract class Renderer {
protected $someProperty;
public abstract function render(MyObject $obj);
}
class RendererA extends Renderer {
public function render(MyObject $obj) {
return $this->someProperty . ': ' . $obj->name;
}
}
// This is the problematic case ------|
class RendererB extends Renderer { // |
public function render(MySubObject $obj) {
return $this->someProperty . ': ' . $obj->name . ' ' .$obj->subProperty;
}
}
/* EOF */
Take a look at this:
https://github.com/symfony/Routing/blob/2.4/Router.php#L225
In this code, the Router will match a request, however it has two ways of doing this depending on the type of Matcher it has an instance of.
That case uses interfaces to differentiate between the object type, however to perform the same technique to your case try this:
class MyObject {
public $name;
}
class MySubObject extends MyObject {
public $subProperty;
}
class Renderer {
public function render(MyObject $obj) {
if($obj instanceof MySubObject) {
return $this->someProperty . ': ' . $obj->name . ' ' .$obj->subProperty;
}
return $this->someProperty . ': ' . $obj->name;
}
}
Assuming that is the only variation of functionality, you don't need an AbstractRenderer.
Since MySubObject extends MyObject it is still type safe to use the extending class as an argument.
EDIT:
This example uses a basic form of the visitor pattern.
The Renderer essentially becomes a parent manager of multiple potential handlers.
The input is handled differently by drivers added at the configuration stage depending of the input's attributes (class in this case, but there are more advanced forms of criteria).
This example can be executed outside of namespaces.
interface TemplateInterface
{
/**
* #return string
*/
public function getName();
}
interface DriverInterface
{
/**
* #param TemplateInterface $template
* #return string
*/
public function doRender(TemplateInterface $template);
}
class Renderer
{
/**
* #var array
*/
protected $drivers;
protected $property = 'Sample Property';
/**
* Constructor
*/
public function __construct()
{
$this->drivers = array();
}
/**
* #param TemplateInterface $template
* #return string
*/
public function render(TemplateInterface $template)
{
$class = get_class($template);
if(false === isset($this->drivers[$class])) {
throw new InvalidArgumentException(sprintf('Renderer Driver supporting class "%s" is not present.', $class));
}
return sprintf('%s: %s', $this->property, $this->drivers[$class]->doRender($template));
}
/**
* #param DriverInterface $driver
* #param $class
* #return $this
*/
public function addDriver(DriverInterface $driver, $class)
{
$this->drivers[$class] = $driver;
return $this;
}
}
class MyTemplate implements TemplateInterface
{
public function getName()
{
return 'my_template';
}
}
class MyOtherTemplate implements TemplateInterface
{
public function getName()
{
return 'my_other_template';
}
public function getOtherProperty()
{
return 'this is another property';
}
}
class MyDriver implements DriverInterface
{
public function doRender(TemplateInterface $template)
{
return $template->getName();
}
}
class MyOtherDriver implements DriverInterface
{
public function doRender(TemplateInterface $template)
{
if(false === $template instanceof MyOtherTemplate) {
throw new InvalidaArgumentException('OtherDriver::doRender argument must be an instance of MyOtherTemplate');
}
return sprintf('%s %s', $template->getName(), $template->getOtherProperty());
}
}
$renderer = new Renderer();
$renderer
->addDriver(new MyDriver(), 'MyTemplate')
->addDriver(new MyOtherDriver(), 'MyOtherTemplate')
;
echo '<pre>';
echo $renderer->render(new MyTemplate()).PHP_EOL;
echo $renderer->render(new MyOtherTemplate());
This is a more advanced example.
This time you don't specify the class of the template, instead you only add the driver, but this driver interface required the supports method be implemented. The renderer will loop through each driver asking them if they support the template.
The first driver to return true will render and return the template.
interface DriverInterface
{
/**
* #param TemplateInterface $template
* #return string
*/
public function doRender(TemplateInterface $template);
/**
* #param TemplateInterface $template
* #return bool
*/
public function supports(TemplateInterface $template);
}
class Renderer
{
/**
* #var array
*/
protected $drivers;
protected $property = 'Sample Property';
/**
* Constructor
*/
public function __construct()
{
$this->drivers = array();
}
/**
* Returns the rendered template, or false if the template was not supported by any driver.
*
* #param TemplateInterface $template
* #return string|false
*/
public function render(TemplateInterface $template)
{
$class = get_class($template);
foreach($this->drivers as $driver) {
if($driver->supports($template)) {
return sprintf('%s: %s', $this->property, $driver->doRender($template));
}
}
return false;
}
/**
* #param DriverInterface $driver
* #param $class
* #return $this
*/
public function addDriver(DriverInterface $driver)
{
$this->drivers[] = $driver;
return $this;
}
}