Does it hurt Demeter's law when model refers to another model? - php

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

Related

Doctrine DBAL Visitor pattern

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()?

Getting the child-type of an object from a method inherited from the father

my problem is getting the right type of object from a method, which is returning a "mixed" type due to inhreitance.
I've got a generic list class
class List
{
/**
* #var Item[]
*/
protected $items;
public function __construct()
{
$this->items = array();
}
/**
* #return Item[]
*/
public function getAll()
{
return $this->items;
}
/**
* #return Item
*/
public function getOne($index)
{
if (isset($this->items[$index])) {
return $this->items[$index];
}
return null;
}
}
containing element of type Item, which is a generic class either
class Item
{
/**
* #var string
*/
protected $name;
public function __construct($name)
{
$this->name = $name;
}
}
Such generic classes are extended by N different lists. Just an example
class UserList extends List
{
/* user-specific implementation */
}
class User extends Item
{
/* user-specific implementation */
}
In the client code
$user_list = new UserList();
foreach ($user_list->getAll() as $user) {
echo $user->getEmailAddr();
}
Inside the foreach I don't have code completion, because my getAll method (inherited from the father) is returning Item[], or mixed[], not a User[]. Same problem with getOne method.
I wouldn't like to have to override such methods.
Is there a more clever and elegant solution?
Thank you
I don't think there's any way for the IDE to infer the type automatically. Use a phpdoc type annotation:
foreach ($user_list->getAll() as $user) {
/** #var User $user */
echo $user->getEmailAddr();
}
See the related question PHPDoc type hinting for array of objects?

Doesn't it hurt Demeter's law when using services/factories in model?

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:
Services::getForumThread()->findBadLanguage($comment);
}
}
I know findBadLanguage() should be in another class, but lets suppose thats okay. Lets focus on Services::get****() calls. Is it OK to turn to a global container and get objects from it? Or to turn to a factory? Doesnt it hury Demeter's law? It says we must not use object from the outside

Paris ORM, has_many_through with restrictions

What's the best way to approach this in Paris ORM?
I have a set of categories and a set of supplier profiles that havw a column called reatured. Currently my class is as follows:
<?php
namespace {
/**
* Class Category
*/
class Category extends ConfettiModel
{
public static $_table = 'supplier_directory_category';
/**
* Returns only top level categories - they have no parent
*
* #return bool
*/
public static function topLevel()
{
return self::where('parent', 0);
}
public static function marketing()
{
return self::where('marketing', 'Yes');
}
public function getTable() {
return self::$_table;
}
/**
* Is this a top level category - has no parent
*
* #return bool
*/
public function isTopLevel()
{
return ($this->parentId == 0);
}
/**
* Associated DirectoryProfile's
*
* #return ORMWrapper
*/
public function profiles()
{
return $this->has_many_through('DirectoryProfile', 'CategoryDirectoryProfile', 'category', 'supplier');
}
}
I'd like to add a new function, featuredProfiles() that allows me to retrieve the same results as profiles(), but in this case I want to restrict it to suppliers with featured = 'Yes'.
I'm not quite sure how to make this happen.
I took a punt and the answer was easier than I anticipated:
public function featuredProfiles() {
return $this->profiles()->where('featured', 'Yes');
}
The where is added as part of the query on the joined table.

PHP classes and functions/ undefined variable

Why do I get undefined property Takeover::user2 on function takeover?
I'm not sure what I'm doing wrong. Can someone help?
I can call user2->addsaldo() on main file but I can't call it inside another function. Why?
Class user
class User {
/**
* #AttributeType int
*/
private $iduser;
/**
* #AttributeType float
*/
private $saldo=0;
/**
* #AssociationType Portefolio
* #AssociationKind Composition
*/
public $idportefolio;
public function __construct($iduser){
$this->iduser = $iduser;
}
/**
* #access public
*/
public function getid() {
// Not yet implemented
}
/**
* #access public
*/
public function addsaldo($saldo) {
$this->saldo = $saldo;
}
}
Class takeover
class Takeover {
/**
* #AttributeType int
*/
private $idTakeover;
/**
* #AssociationType root
* #AssociationMultiplicity 1
*/
public $Root;
public $IdeasTakerover=array();
public function __construct($idTakeover){
$this->idTakeover = $idTakeover;
}
/**
* #access public
*/
public function GetIdCompraRoot() {
// Not yet implemented
}
public function AddIdeasTakeover($idTakeover, $idideia) {
$this->idTakeover = $idTakeover;
$this->idideia = $idideia;
array_push($this->IdeasTakerover,$idideia);
}
/**
* #access public
*/
public function Takeover() {
$this->user2->addsaldo(200); //USER2 DOES EXIST
}
}
Creating users and calling them:
$takeover = new Takeover(1);
for ($i=0; $i<$conta; $i++ ){
$takeover->AddIdeasTakeover(1,$idsideias[$i]);
}
$takeover->Takeover();
if ($partial == "user") {
$booleanUser = TRUE;
$iduser=substr($buffer, 4);
${'user'.$iduser} = new User($iduser);
}
The problem is not in this class. The problem is that $GLOBALS['user2'] is not defined when referenced here: $GLOBALS['user'.$this->IdeiasTakeover[$i]]. You then call addsaldo() on an undefined array element in $GLOBALS.
On another note, it is impossible to write good code using $GLOBALS. Global variables are bad news. You should not be using/referencing global variables. The exception would be at a low level when referencing $_POST, $_GET, etc. Even still, all the good PHP frameworks wrap these in a request object.
Edit
Dependency Injection is a better way for one class to use another:
class X {
$yInstance;
public function __construct($yInstance)
{
$this->yInstance = $yInstance;
}
public function x()
{
//Call your 'y' method on an instance of Y
$this->yInstance->y();
}
}
class Y {
public function y()
{
echo 'Y::y() called!';
}
}
Call X::x()
$y = new Y();
$x = new X($y);
$x->x();
Output:
Y::y() called!
The easiest way to do this is to use a dependency injection container. Here is my favorite one for PHP: http://symfony.com/doc/current/components/dependency_injection/introduction.html
Also, check out Martin Fowlers classic article about IOC:
http://martinfowler.com/articles/injection.html
Good luck!

Categories