I've class called Books
class Books {
/* Member variables */
var $price;
var $title;
function __construct( $title, $price ) {
$this->title = $title;
$this->price = $price;
}
/* Member functions */
function getPrice(){
echo $this->price ."<br/>";
}
function getTitle(){
echo $this->title ." <br/>";
}
}
then I've added another class which inherit my Book class something like this
class Novel extends Books {
var $publisher;
function getPublisher(){
echo $this->publisher. "<br />";
}
function __construct( $publisher ) {
$this->publisher = $publisher;
}
}
Now I want to invoke Novel class and set all of its properties such as title, price and publisher via constructor so if I do this
$physics = new Books("Physics for High School",1);
$testNovel = new Novel("Test Publisher");
it works great it set the publisher value of my $testNovel object
so how can I set value of Title and price while creating object of it??
even If I try
$testNovel = new Novel("Test Title",4,"Test Pubisher");
here "Test Title" is set as publisher instead of "Test Publisher". Moreover, if I put more values inside signature something like this
$testNovel = new Novel("Test Title",4,"New Pub","","Whatever","","Why it allow");
it does not throw any error why??
When you extend a class that defines a constructor, with a class that defines it's own constructor, you need to call the parent constructor yourself supplying the required parameters. E.g.:
class Novel extends Books
{
// ...
function __construct($title, $price, $publisher)
{
$this->publisher = $publisher;
parent::__construct($title, $price);
}
}
From the manual:
Note: Parent constructors are not called implicitly if the child class
defines a constructor. In order to run a parent constructor, a call to
parent::__construct() within the child constructor is required. If the
child does not define a constructor then it may be inherited from the
parent class just like a normal class method (if it was not declared
as private).
Related
file:Element.php
<?php
use Doctrine\Common\Collections\{
Collection,
ArrayCollection
};
use Doctrine\ORM\Mapping\{
Id,
GeneratedValue,
Table,
Mapping,
Entity,
Column,
JoinColumn,
ManyToOne
};
use Doctrine\DBAL\Types\Types;
#[Entity]
#[Table(name: 'elements')]
class Element
{
#[Id, GeneratedValue, Column(type: Types::INTEGER)]
protected $id;
// OWNER SIDE
#[ManyToOne(targetEntity: \SomeClass::class, inversedBy: 'elementsCollection')]
#[JoinColumn(name: 'some_class_id')]
protected $someClass;
public function getSomeClass()
{
return $this->someClass;
}
public function removeSomeClass()
{
$this->someClass = NULL;
}
}
file: SomeClass.php
<?php
use Doctrine\Common\Collections\{
Collection,
ArrayCollection
};
use Doctrine\ORM\Mapping\{
Id,
GeneratedValue,
Table,
Mapping,
Entity,
Column,
OneToMany
};
use Doctrine\DBAL\Types\Types;
#[Entity]
#[Table(name: 'some_classes')]
class SomeClass
{
#[Id, GeneratedValue, Column(type: Types::INTEGER)]
protected $id;
public function __construct()
{
$this->elementsCollection = new ArrayCollection;
}
// INVERSE SIDE
#[OneToMany(targetEntity: \Element::class, mappedBy: 'someClass')]
protected Collection $elementsCollection;
public function getElementsCollection()
{
return $this->elementsCollection;
}
}
file: index.php
$em = DI::get('mainOEM');
// IdentityMap knows about this $el, because I downloded its.
$el = $em->find(\Element::class, 1);
DUMP
\var_dump($el->getSomeClass()); // object(DoctrineProxies\__CG__\SomeClass)...
$el->removeSomeClass();
DUMP
\var_dump($el->getSomeClass()); // null
SomeClass Entity contains collection of Elements (SomeClass->elementsCollection is inverse side)
$someClassOjb = $em->find(\SomeClass::class, 1);
call PersistentCollection->removeElement($el)
$someClassOjb->getElementsCollection()->removeElement($el);
Now, I just removed $el from the $someClassOjb's collection.
But... $el is updated, it isn't NULL now.
DUMP
// object(DoctrineProxies\__CG__\SomeClass)... like in the beginning
\var_dump($el->getSomeClass());
Ok, IdentityMap was updated by PersistentCollection by getting Elements of SomeClass relationship...
BUT Why PersistentCollection updated $el which I'd already got earlier?!?! I'd already downloaded its from the schema!
How does PersistentCollection update IdentityMap?
I am very new to TDD. I am using phpunit 7.4x-dev. I have the following abstract class that I am trying to develop unit tests for.
use PDO;
abstract class Model {
protected static function getDB() {
static $db = null;
if ($db === null) {
$db = new PDO(ConfigDatabase::DSN, ConfigDatabase::USER, ConfigDatabase::PASSWORD);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return $db;
}
}
I have created the following test to get around the need to deal with the static protected method. And it works if I provide "ConfigureDatabase" class.
use PHPUnit\Framework\TestCase;
class ModelTest extends TestCase {
function newMockClass(){
$stub = new class() extends Model{
function getStaticMethod($methodName){
return self::$methodName();
}
};
return $stub;
}
public function testDatabaseExists() {
$stub = $this->newMockClass();
$db = $stub->getStaticMethod('getDB');
$this->assertInstanceOf(PDO::class,$db);
}
}
Since I do not want my tests to rely on any actual database, How would I fake the calls to PDO.
Following Dormilich suggestion I developed a database interface, just in case I decide later I do not want to use PDO.
interface CRUDImp {
function __construct($datbaseBridgeLikePDO);
...
}
Next I wrote my tests for the constructor. I used setup to make sure I was starting with a fresh mock of \PDO.
class PDOWrapperTest extends TestCase {
private $pdoMock;
private $db;
function setup() {
$this->pdoMock = $this->createMock('\PDO');
$this->db = new PDOWrapper($this->pdoMock);
}
public function testWrapperExists() {
$this->pdoMock->method('getAttribute')->willReturn(\PDO::ERRMODE_EXCEPTION);
$db = new PDOWrapper($this->pdoMock);
$x = $db instanceof CRUDImp;
$this->assertTrue($x);
}
/**
* #expectedException \Exception
*/
public function testNonPDOPassedToConstructor() {
$mock = $this->createMock('\Exception');
$x = new PDOWrapper($mock);
}
...
}
Since PHP is loosely typed I check to make sure that the class passed to the constructor was an instance of \PDO. I implemented the concrete class as follows
class PDOWrapper implements CRUDImp {
private $pdo;
private $dataOutputType = \PDO::FETCH_ASSOC;
public function __construct($pdo) {
if (!($pdo instanceof \PDO)) {
throw new \Exception("PDOWrapper must be passed instance of \PDO");
}
$attr_Errmode = $pdo->getAttribute(\PDO::ATTR_ERRMODE);
if ($attr_Errmode !== \PDO::ERRMODE_EXCEPTION) {
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
}
$this->pdo = $pdo;
}
...
}
Now that I have an independent database wrapper the original Model tests are at the moment trivial and no longer needed. The abstract class Model was modified as follows:
abstract class Model {
protected $database=null;
function __construct(CRUDWrapper $database) {
$this->database = $database;
}
...
}
So for those not familiar with dependency injection I found the following links helpful:
http://php-di.org/doc/understanding-di.html
https://codeinphp.github.io/post/dependency-injection-in-php/
https://designpatternsphp.readthedocs.io/en/latest/Structural/DependencyInjection/README.html
Hope this shortens someone's work.
I have a REST API. I need to create presentation (DTO) object, but the construction of this object depends on request - it differs in 15%.
I wonder what pattern should I use.
My case:
//presentation-DTO
class Item {
private $name;
private $price;
private $tags;
private $liked; //is Liked by logged user
...
public function __construct(Item $item, bool $liked, ...)
{
$this->name = $item->getName();
$this->price = $item->getPrice();
$this->tags = $item->getTags();
$this->liked = $liked;
...
}
}
When user is not logged in - I don't need $liked
When showing list of items - I don't need $tags
And there are more attributes that works as above.
My first idea was to use Builder principle.
$itemBuilder = new ItemBuilder();
$itemBuilder->setItem($item);
...
if($user) {
$itemBuilder->setUserLiked($userLiked);
...
}
return $itemBuilder->build();
It solves my problem with too many parameters in constructor.
But still, I also don't need all parameters to be constructed - eg. I don't need tags (on lists). As I use lazy load, I don't want my dto constructor to call them.
So I thought, maybe Factory.. but then my problem with too many (and optional) parameters is returning.
How will you solve this?
Sorry I don't have required points to make a comment hence an answer.
What are you trying to do with the Item class. Your class is Item and first parameter is also of type Item. I cannot visualizes how its going to work.
I will prefer to keep business login to set proper properties in a separate class:
/**
* A class for business logic to set the proper properties
*/
class ItemProperties {
private $item;
public $isLogin = false;
public $showList = false;
.....
public function __construct(Item &$item) {
// set all properties;
}
public function getProperties() {
$retVal = [];
if($this->isLogin == true) {
$retVal['liked'] = true;
}
if($this->showList == true) {
$retVal['tags'] = $this->item->getTags();
}
if(....) {
$retVal['...'] = $this->item->.....();
}
return $retVal;
}
}
/**
* DTO
*/
class Item {
public function __construct(ItemProperties $itemProps) {
$this->setItemProps($itemProps);
}
// If you prefer lazy loading here...maybe make it public
// and remove call from constructor.
private function setItemProps(&$itemProps) {
$properties = $itemProps->getProperties();
foreach($properties AS $propName => $propValue) {
$this->$propName = $propValue;
}
}
}
// Usage:
$itemProps = new ItemProperties($Item);
// set other properties if you need to...
$itemProps->isLogin = false;
$item = new Item($itemProps);
I was wondering if there is a way that i can initialize the property owner with an entity User of FOSUserBundle so that it contains the user who created the Post
I want to do this inside the constructor as shown below.
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="post")
* #ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
*/
class Post
{
/* here are defined some attributs */
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="posts")
* #ORM\JoinColumn(name="owner", referencedColumnName="id")
*/
private $owner;
public function __construct()
{
$this->owner = /* get current user */ ;
}
}
Is there a way to do this by replacing the comment in the constructor with something ?
Thank you for your answers
No, there isn't. [*]
There are at least two ways to deal with this:
Create your Post entities through a factory service which populates the
owner property:
namespace My\Bundle\EntityFactory;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use My\Bundle\Entity\Post;
class PostFactory
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function createPost()
{
$user = $this->tokenStorage()->getToken()->getUser();
$post = new Post($user);
}
}
(for this example, you will have to modify your Post constructor to
accept the owner as a parameter)
In services.yml:
services:
post_factory:
class: My\Bundle\EntityFactory\PostFactory
arguments: [#security.token_storage]
To create an entity from your controller:
$post = $this->container->get('post_factory')->createPost();
If you can tolerate that the owner will only be set once you persist the
entity, you can use a doctrine event listener:
namespace My\Bundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use My\Bundle\Entity\Post;
class PostOwnerAssignmentListener
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function prePersist(LifecycleEventArgs $event)
{
$entity = $event->getEntity();
if ($entity instanceof Post && !$entity->getOwner()) {
$entity->setOwner($this->tokenStorage->getToken()->getUser());
}
}
}
In services.yml:
services:
post_owner_assignment_listener:
class: My\Bundle\EventListener\PostOwnerAssignmentListener
arguments: [#security.token_storage]
tags:
- { name: doctrine.event_listener, event: prePersit }
The advantage here is that the owner gets assigned no matter how and where
the Post is created.
[*]: Well, technically with the default app.php you could access the
kernel by declaring global $kernel; in your constructor and go from there,
however this is very strongly discouraged and may break in strange and subtle
ways.
I think you are way over-complicating this issue. When you create a new Post in your controller, either in the controller or in the repository do something like this:
use AppBundle\Entity\Post; //at top of controller
$em = $this->getDoctrine()->getManager();
$user = $this->container->get('security.token_storage')->getToken()->getUser();
$post = new Post();
$em->persist( $post );
$post->setOwner( $user );
// set other fields in your post entity
$em->flush();
For Symfony 4+ with Autowiring and Entity event listener:
In /EventListener/PostPrePersistListener.php:
namespace App\EventListener;
use App\Entity\Post;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class PostPrePersistListener
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function prePersist(Post $post, LifecycleEventArgs $event)
{
$post->setOwner($this->tokenStorage->getToken()->getUser());
}
}
In services.yaml:
services:
App\EventListener\PostPrePersistListener:
autowire: true
tags:
- { name: doctrine.orm.entity_listener, entity: 'App\Entity\Post', event: prePersist }
Modifying services.yaml is required as Symfony cannot know that this custom service is tagged to hook on doctrine.event_listener
This works at Entity-level as asked, to ensure Controller do not handle the owner value.
In short.. question is... "Say what?" To expand... "I don't get the error"
Strict Standards: Non-static method Pyro\Template::preLoad() should not be called statically, assuming $this from incompatible context in /opt/lampp/htdocs/dc/pyro/app/controllers/admin/courses.php on line 14
public function actionIndex() {
$this->data->users = $this->DB->query("SELECT id, name, description FROM :#courses")->getAll();
$this->data->title = 'Courses';
$this->data->content_area = \Pyro\Template::preLoad('admin/courses/index', $this->data); // Line 14
}
Template... its incomplete...
<?php
namespace Pyro;
class Template {
// Stores default master template
public static $defaultTemplate = 'template.php';
public function preLoad($template, $page) {
ob_start();
include( VIEWS . "{$template}.php");
$buffer = ob_get_contents();
#ob_end_clean();
return $buffer;
}
public function load($page) {
include( VIEWS . self::$defaultTemplate);
}
}
Why does this error appear? Cheers
Well the preLoad function is not static. Which means only an object of the class Template can use this method. A static method exists indepedently of any object of the class.
Template::preLoad is a static call : you didn't create a Template object, then call the preLoad method. So basically, you've got two solutions :
Making preLoad static;
Creating a Template object, and then call its preLoad function.
preLoad function should be static
public static function preLoad($template, $page) {
preLoad function isn't static. ti should look like this:
public static function preLoad($template, $page) {
ob_start();
include( VIEWS . "{$template}.php");
$buffer = ob_get_contents();
#ob_end_clean();
return $buffer;
}
Like everyone said, the you called the function with as a static method:
Template::preLoad(xxx)
The :: means static in PHP. Functions are typically called as static :: or object -> calls.
The function definition is one or the other:
public static function preLoad($template, $page)
Called like: Template::preLoad('admin/courses/index', $this->data);
OR
public function preLoad($template, $page)
Called like Template->preLoad('admin/courses/index', $this->data);
For reference, a static function is able to be called without instantiating an object. If your function doesn't need an object to run, you could make it static. Basically, this means you can't reference $this in a static method. It will run with the given inputs without having to construct the object.