I got two entities. One is a WebshopItem entity, the other one is a WebshopPrice entity.
Each time, you are creating a WebshopItem, you are also filling in 3 WebshopPrices. The WebshopPrices are 3 currencies (EUR, USD and GBP).
Based on the currency you selected (and is saved in your session) I want to display the currency you selected. So, if you picked EUR, I of course want to display the EUR price.
What's the general way of doing this in symfony? Should I use a twig extension which returns the price from the WebshopItem object, based on what's in your session? Should I already filter the WebshopPrices from the database?
Looking forward to your best solutions. Thanks!
Entity/WebshopItem.php
class WebshopItem
{
/**
* #var \Doctrine\Common\Collections\Collection
*/
private $prices;
etc....
}
Entity/WebshopItemPrice.php
class WebshopItemPrice
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $currency;
/**
* #var string
*/
private $price;
private $webshopItem;
}
UPDATE
You can use an entity listener too, but in that case you'll need to override the default resolver to get the session in your listener:
src/Your/GreatBundle/Resources/config/services.yml
doctrine.orm.default_entity_listener_resolver:
class: Your\GreatBundle\Listener\EntityListenerResolver
arguments: [#service_container]
src/Your/GreatBundle/Listener/EntityListenerResolver
namespace Your\GreatBundle\Listener;
use Doctrine\ORM\Mapping\EntityListenerResolver as EntityListenerResolverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class EntityListenerResolver implements EntityListenerResolverInterface
{
private $instances = [];
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function clear($className = null)
{
if ($className === null) {
$this->instances = [];
return;
}
if (isset($this->instances[$className = trim($className, '\\')])) {
unset($this->instances[$className]);
}
}
public function register($object)
{
if ( ! is_object($object)) {
throw new \InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object)));
}
$this->instances[get_class($object)] = $object;
}
public function resolve($className)
{
if (isset($this->instances[$className = trim($className, '\\')])) {
return $this->instances[$className];
}
// Here we are injecting the entire container to the listeners
return $this->instances[$className] = new $className($this->container);
}
}
You might listen to the Doctrine's postLoad event in a service injected with the user's session:
src/Your/GreatBundle/Resources/config/services.yml
services:
price.listener:
class: Your\GreatBundle\Listener\PriceListener
arguments: [#session]
tags:
- { name: doctrine.event_listener, event: postLoad }
src/Your/GreatBundle/Listener/PriceListener.php
namespace Your\GreatBundle\Listener\PriceListener;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Your\GreatBundle\Entity\WebshopItem;
class PriceListener
{
private $session;
public function __construct(SessionInterface $session)
{
$this->session = $session;
}
public function postLoad(LifecycleEventArgs $event)
{
$entity = $event->getEntity();
if ($entity instanceof WebshopItem) {
$currency = $this->session->get('currency', 'EUR');
$entity->setCurrency(currency);
}
}
}
src/Your/GreatBundle/Entity/WebshopItem.php
namespace Your\GreatBundle\Entity;
class WebshopItem
{
...
// You don't need to persist this...
private $currency = 'EUR';
public function setCurrency($currency)
{
$this->currency = $currency;
}
public function getPrice()
{
foreach ($this->prices as $price) {
if ($price->getCurrency() === $this->currency) {
return ($price->getPrice();
}
}
return null;
}
}
You can query WebshopItem joined with WebshopItemPrice WHERE WebshopItemPrice.currency = $variableFromSesion. For example:
$queryBuilder
->select('WebshopItem, WebshopItemPrice')
->leftJoin('WebshopItemPrice.prices', 'WebshopItemPrice')
->where('WebshopItemPrice.currency = :currency')
->setParameter('currency', $variableFromSesion)
Where $variableFromSesion is a current user currency stored in session. After executing that query (by getResult() ) you can get price by calling $webshopItem->getPrices() - this should return only one result
The trick is not about how to retrieve the data but what to use to do it. Since this depends on the Request object you should go with something service-ish. If you're sure you'll never never use this elsewhere, then go with the twig extension. If your backend code would use it too then use a service, say MyPricePickerService (and get that in the twig extension). Note on twig extension that I cannot find anything that warrants that a new instance is created for every scope, so upon every call to this operation you should use the injected Container (so not a cached Request, or a previous instance of MyPricePickerService)!
Related
I started to wonder about what exactly is the purpose of service providers in Laravel, and why they work in the way they do. After searching through some articles,
the key points of service providers in my understanding are:
Simplifies object creation (Laravel What is the use of service providers for laravel)
Decoupling your code (r/laravel: When to use service providers?)
Dependency injection
Reduces technical debt
So it basically binds an implementation to an interface, and we can use it by
$app(MyInterface::class)
or something like that, and we can just change the implementation when needed, only in one place, and the rest of our code which depends on it won't break.
But i still can not grasp the concept, why they are the way they are, it seems overcomplicated. I peaked in to the code, it was certainly a ton of work to make Service Providers & Containers work, so there must be a good reason.
So to learn further, i tried to make my own, more simple version of it, which achieves the same goals. (i obviously lack a lot of info on this, and most probably missed some other goals)
My question is, why would this implementation would not satisfy the same use cases?
Service.php
namespace MyVendor;
/**
* Abstract class for creating services
*/
abstract class Service
{
/**
* Holds the instance of the provided service
*
* #var mixed
*/
private static mixed $instance = null;
/**
* Retrieves the instance of the provided service & creates it on-demand
*
* #return mixed
*/
public static function get(): mixed
{
if (self::$instance === null) {
self::$instance = static::instantiate();
}
return self::$instance;
}
/**
* A function which contains the service's object creation logic
*
* #return mixed
*/
abstract protected static function instantiate(): mixed;
}
Example implementation:
For the example, i chose an interface to parse environment variables, as i already had phpdotenv in my project as a dependency
Services/DotenvParser/DotenvParserInterface.php
namespace MyVendor\Services\DotenvParser;
/**
* This is the service interface i want to provide
*/
interface DotenvParserInterface
{
public function parse(string $directory, string $fileName = ".env"): array;
}
Now i will have 2 implementations of this class. I will pretend that a lot of my code already depends on DotenvParserInterface. An old, hacky one which "depends" on another thing, and the replacement for it which uses phpdotenv
A quick fake dependency:
Services/DotenvParser/Dependency.php
namespace MyVendor\Services\DotenvParser;
class Dependency
{
private bool $squeeze;
public string $bar;
public function __construct(string $foo, bool $squeeze)
{
$this->squeeze = $squeeze;
$this->bar = $foo;
if($this->squeeze){
$this->bar .= " JUICE";
}
}
}
Our old code:
Services/DotenvParser/OldDotenvCode.php
namespace MyVendor\Services\DotenvParser;
use BadMethodCallException;
use InvalidArgumentException;
class OldDotenvCode implements DotenvParserInterface
{
/**
* Our fake dependency
*
* #var Dependency
*/
private Dependency $foo;
private string $dir;
private string $fileName;
private string $contents;
private array $result;
public function __construct(Dependency $myDependency)
{
$this->foo = $myDependency;
}
/**
* Implementation of DotenvParserInterface
*
* #param string $directory
* #param string $fileName
* #return array
*/
public function parse(string $directory, string $fileName = ".env"): array
{
try{
$this->setDir($directory)->setFileName($fileName);
}catch(BadMethodCallException $e){
throw new InvalidArgumentException($e->getMessage(), 0, $e);
}
$this->getEnvContents();
$this->contents = $this->getEnvContents();
$this->result = [];
foreach(explode("\n", $this->contents) as $line){
$exploded = explode("=", $line);
$key = $exploded[0];
$value = (isset($exploded[1])) ? trim($exploded[1], "\r") : "";
if($this->foo->bar === "ORANGE JUICE"){
$value = trim($value, "\"");
}
$this->result[$key] = $value;
}
return $this->result;
}
#region Old, bad stuff
public function setDir(string $directory): self{
if(!\is_dir($directory)){
throw new InvalidArgumentException("Directory $directory is not a valid directory");
}
$this->dir = rtrim($directory, "/");
return $this;
}
public function setFileName(string $fileName): self{
if(empty($this->dir)){
throw new BadMethodCallException("Must call method setDir() first with a valid directory path");
}
$fileName = ltrim($fileName, "/");
if(!\file_exists($this->dir . "/" . $fileName)){
throw new InvalidArgumentException("File $fileName does not exist in provided directory {$this->dir}");
}
$this->fileName = $fileName;
return $this;
}
private function getFilePath(): string{
if(empty($this->fileName)){
throw new BadMethodCallException("Must call method setFileName() first");
}
return $this->dir . "/" . $this->fileName;
}
private function getEnvContents(): string{
return \file_get_contents($this->getFilePath());
}
public function setup(): void
{
$this->setDir($directory)->setFileName($fileName);
}
#endregion
}
Now, the phpdotenv version
Services/DotenvParser/phpdotenv.php
namespace MyVendor\Services\DotenvParser;
use Dotenv\Dotenv;
use InvalidArgumentException;
use Dotenv\Dotenv;
use InvalidArgumentException;
class phpdotenv implements DotenvParserInterface
{
public function parse(string $directory, string $fileName = ".env"): array
{
try{
Dotenv::createMutable($directory, $fileName)->load();
}catch(\Dotenv\Exception\InvalidPathException $e){
throw new InvalidArgumentException($e->getMessage(), 0, $e);
}
$result = $_ENV;
$_ENV = []; //Hehe
return $result;
}
}
Our service which we made from extending our Service class
Services/DotenvParser/DotenvParserService.php
namespace MyVendor\Services\DotenvParser;
use MyVendor\Service;
class DotenvParserService extends Service
{
// We can do this to make type hinting for ourselves
public static function get(): DotenvParserInterface
{
return parent::get();
}
protected static function instantiate(): DotenvParserInterface
{
$year = 2022;
// Some condition, to return one or another
if($year < 2022){
$dep = new \MyVendor\Services\DotenvParser\Dependency("ORANGE", true);
return new OldDotenvCode($dep);
}
return new phpdotenv();
}
}
And now, we can use it like this:
$dotenvparser = \MyVendor\Services\DotenvParser\DotenvParserService::get();
$result = $dotenvparser->parse(__DIR__);
var_dump($result);
// Outputs an array of our environment variables, yey!
We can also write tests for our services to see if anything breaks:
namespace MyVendorTest\Services\DotenvParser;
use InvalidArgumentException;
use MyVendor\Services\DotenvParser\DotenvParserInterface;
use MyVendor\Services\DotenvParser\DotenvParserService;
final class DotenvParserServiceTest extends \PHPUnit\Framework\TestCase
{
public function doesInstantiate(): void
{
$testParser = DotenvParserService::get();
$this->assertInstanceOf(DotenvParserInterface::class, $testParser);
}
public function testWorksFromValidDirNFile(): void
{
// The actual contents of a .env file
$testArray = [
"DEV_MODE" => "TRUE",
"BASE_HREF" => "http://localhost:8080/"
];
$testParser = DotenvParserService::get();
// phpdotenv loads every parent .env too and i was having none of it for this quick demonstration
$result = $testParser->parse(__DIR__."/../../../", ".env");
$this->assertEquals($testArray, $result);
}
public function testSetupFromInvalidDir(): void
{
$this->expectException(InvalidArgumentException::class);
$testParser = DotenvParserService::get();
$testParser->parse("i_am_a_dir_which_does_not_exist");
}
public function testSetupFromInvalidFile(): void
{
$this->expectException(InvalidArgumentException::class);
$testParser = DotenvParserService::get();
$testParser->parse(__DIR__, ".notenv");
}
}
So this ended up quite lenghty, but after having that Service class, you basically only need: An interface, at least one implementation of that interface, and a service class which instantiates an implementation of that interface, and optionally some tests for it. And, you can even do dependency injection with it (??) (circular dependencies would get us stuck in an endless loop), like this:
protected static function instantiate(): FooInterface
{
//BarService & AcmeService are extending our Service class
return new FooInterface(BarService::get(), AcmeService::get(), "ORANGE JUICE")
}
I am ready to absorb massive amounts of information
What other things Laravel's Service providers & containers do than i am aware of?
Why and how is it better than a simpler version, like this one?
Does my version really achieve at least those 4 key points i mentioned in the start?
I have a unit test class in which I want to instantiate a object from another class in order to that I used setUpBeforeClass() fixtures of phpunit. So if I will use that recently instantiated object directly in test function then its working fine.
If i'll use this object into another function which had been created for data providers. So that object sets to null cause providers always execute first.
Is there a way to call dataProviders just before the test runs, instead?
require_once('Dashboard.php');
Class Someclass extends PHPUnit_Framework_TestCase {
protected static $_dashboard;
public static function setUpBeforeClass()
{
self::$_dashboard = new Dashboard();
self::$_dashboard->set_class_type('Member');
}
/**
* Test Org Thumb Image Existense
* param org profile image : array
* #dataProvider getOrgProfileImages
*/
public function testFieldValidation($a,$b){
//If I call that object function here it will give the result.
//$members = self::$_dashboard->get_members();
//var_dump($members); Printing result as expected
$this->assertTrue(true);
}
public function getOrgProfileImages() : array {
//var_dump(self::$_dashboard);
$members = self::$_dashboard->get_members();
$tmp_array = ['2','2'];
return $tmp_array;
}
public static function tearDownAfterClass()
{
self::$_dashboard = null;
}
}
Error:
The data provider specified for Someclass::testFieldValidation is invalid.
Call to a member function get_members() on null
Please help to mitigate this issue.
Note: since I don't have the source of your Dashboard class, I'm using a random number in the examples below instead
Providers are invoked before any tests are run (and before any hooks, including beforeClass have a chance to run). By far the easiest way to achieve what you're after is to populate that static property on the class load:
use PHPUnit\Framework\TestCase;
/** #runTestsInSeparateProcesses enabled */
class SomeTest extends TestCase
{
public static $_rand = null;
public function provider()
{
$rand = self::$_rand;
var_dump(__METHOD__, getmypid(), 'provided rand', $rand);
return ['rand' => [$rand]];
}
/** #dataProvider provider */
public function testSomething($rand)
{
$this->expectNotToPerformAssertions();
var_dump(__METHOD__, getmypid(), 'tested with', $rand);
}
/** #dataProvider provider */
public function testSomethingElse($rand)
{
$this->expectNotToPerformAssertions();
var_dump(__METHOD__, getmypid(), 'tested with', $rand);
}
}
// this runs before anything happens to the test case class
// even before providers are invoked
SomeTest::$_rand = rand();
Or you could instantiate you dashboard in the provider itself, on the first call:
public function provider()
{
// Instantiate once
if (null === self::$_rand) {
self::$_rand = rand();
}
$rand = self::$_rand;
var_dump(__METHOD__, getmypid(), 'provided rand', $rand);
return ['rand' => [$rand]];
}
#dirk-scholten is right. You SHOULD be creating a new object for each test. It's a GOOD testing practice. Frankly it looks more like you are testing the data and not testing the code, which is fine I guess, it's just not the typical use of PHPUnit. Based on the assumption that you want to make sure every user in the database has a thumbnail image (just guessing), I would go with the following:
<?php
class DashboardDataTest extends PHPUnit\Framework\TestCase {
private $dashboard;
public function setUp() {
$this->dashboard = new Dashboard();
}
/**
* Test Org Thumb Image Existence
* param org profile image : array
*
* #dataProvider getOrgProfileImages
*
* #param int $user_id
*/
public function testThumbnailImageExists(int $user_id){
$thumbnail = $this->dashboard->get_member_thumbnail($user_id);
$this->assertNotNull($thumbnail);
}
public function geOrgUserIDs() : array {
$dashboard = new Dashboard();
// Something that is slow
$user_ids = $dashboard->get_all_the_member_user_ids();
$data = [];
foreach($user_ids as $user_id){
$data[] = [$user_id];
}
return $data;
}
}
Each data provider will get called once and only once before the tests. You do not need a static data fixture on the class because phpunit handles the data fixture for you when you use data providers.
Alright so I'm converting a small laravel project to symfony (will get bigger, and the bundling architecture symfony uses will be ideal)
I'm apparently spoiled with laravels facades and eloquent working with existing databases almost right out of the box.
I can't find the most appropriate way to have a wrapper or "helper" class get access to an entities repository.
first let me give a few examples then I will explain what I have attempted. (I'm willing to bounty some points for a good answer but unfortunately the time constraints on the project can't exactly wait)
So in laravel I had all my model classes. Then I created some wrapper / helper classes that would essentially turn the data into something a little more usable (i.e. multiple queries and objects containing more versatile information to work with). And with the magic of facades I could call upon each model and query them without and dependencies injected into these "Helper" classes. keeping them very lean. In symfony it appears the ideal solution is to put all of your reusable database logic in repositories, ok.
In symfony I'm surrounded by Inversion of Control (IoC); which is fine but design pattern is failing to be intuitive for me to fully figure this scenario out. I have tried to create services out every single repository, which works great if being called from a controller or other Dependency Injected (DI) service. But in a standard php class, it appears my hands are tied without passing entity manager to each helper class's constructor. *shivers*
The first limitation is I have zero ability to change the schema of the existing tables (which obviously doesn't change the problem, just don't want anyone to suggest altering the entities).
So how does one accomplish this.
EDIT:
so thanks to #mojo's comment I've pulled off what I wanted to do. Still looking for a better alternative if it exists. (see edit 2 below)
currently I have:
config.yml docterine.orm.entity_managers:
entity_managers:
default:
auto_mapping: true
connection: default
asterisk:
connection: asterisk
mappings:
AsteriskDbBundle: ~
asteriskcdr:
connection: asteriskcdr
mappings:
AsteriskCdrDbBundle:
service.yml
services:
app.services.doctrine.entitymanager.provider:
class: AppBundle\Services\EntityManagerProvider
arguments: [#doctrine]
tags:
- {name: kernel.event_listener, event: kernel.request, method: onKernelRequest}
EntityManagerProvider
namespace AppBundle\Services;
use Doctrine\Bundle\DoctrineBundle\Registry as DoctrineRegistry;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Config\Definition\Exception\Exception;
class EntityManagerProvider
{
/** #var DoctrineRegistry */
private static $doctrine;
public function __construct(DoctrineRegistry $doctrine)
{
static::$doctrine = $doctrine;
}
/**
* #param $class
* #return EntityManager
*/
public static function getEntityManager($class)
{
if(($em = static::$doctrine->getManagerForClass($class)) instanceof EntityManager == false)
throw new Exception(get_class($em) . ' is not an instance of ' . EntityManager::class);
return $em;
}
// oh man does this feel dirty
public function onKernelRequest($event)
{
return;
}
}
Example Controller
$extension = Extension::createFromDevice(DeviceRepository::findById(92681));
ExtendedEntityRepository
namespace AppBundle\Entity;
use AppBundle\Services\EntityManagerProvider;
use AppBundle\Utils\DateTimeRange;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadata;
use Symfony\Component\Config\Definition\Exception\Exception;
class ExtendedEntityRepository extends \Doctrine\ORM\EntityRepository
{
/** #var ExtendedEntityRepository */
protected static $instance;
public function __construct(EntityManager $entityManager, ClassMetadata $class)
{
parent::__construct($entityManager, $class);
if(static::$instance instanceof static == false)
static::$instance = $this;
}
// some horribly dirty magic to get the entity that belongs to this repo... which requires the repos to have the same name and exist one directory down in a 'Repositories' folder
public static function getInstance()
{
if(static::$instance instanceof static == false) {
preg_match('/^(.*?)Repositories\\\([A-Za-z_]*?)Repository$/', static::class, $match);
$class = $match[1] . $match[2];
$em = EntityManagerProvider::getEntityManager($class);
static::$instance = new static($em, $em->getClassMetadata($class));
}
return static::$instance;
}
public static function findById($id)
{
return static::getInstance()->find($id);
}
public static function getQueryBuilder()
{
return static::getInstance()->getEntityManager()->createQueryBuilder();
}
public static function getPreBuiltQueryBuilder()
{
return static::getQueryBuilder()->select('o')->from(static::getInstance()->getClassName(), 'o');
}
public static function findByColumn($column, $value)
{
//if($this->getClassMetadata()->hasField($column) == false)
// throw new Exception($this->getEntityName() . " does not contain a field named `{$column}`");
return static::getPreBuiltQueryBuilder()->where("{$column} = ?1")->setParameter(1, $value)->getQuery()->execute();
}
public static function filterByDateTimeRange($column, DateTimeRange $dateTimeRange, QueryBuilder $queryBuilder = null)
{
if($queryBuilder == null)
$queryBuilder = static::getPreBuiltQueryBuilder();
if($dateTimeRange != null && $dateTimeRange->start instanceof \DateTime && $dateTimeRange->end instanceof \DateTime) {
return $queryBuilder->andWhere(
$queryBuilder->expr()->between($column, ':dateTimeFrom', ':dateTimeTo')
)->setParameters(['dateTimeFrom' => $dateTimeRange->start, 'dateTimeTo' => $dateTimeRange->end]);
}
return $queryBuilder;
}
}
DeviceRepository
namespace Asterisk\DbBundle\Entity\Repositories;
use AppBundle\Entity\ExtendedEntityRepository;
/**
* DeviceRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class DeviceRepository extends ExtendedEntityRepository
{
//empty as it only needs to extend the ExtendedEntityRepository class
}
Extension
namespace AppBundle\Wrappers;
use Asterisk\DbBundle\Entity\Device;
class Extension
{
public $displayName;
public $number;
public function __construct($number, $displayName = "")
{
$this->number = $number;
$this->displayName = $displayName;
}
public static function createFromDevice(Device $device)
{
return new Extension($device->getUser(), $device->getDescription());
}
}
Agent (This is an example of why having repositories access statically is helpful)
namespace AppBundle\Wrappers;
use AppBundle\Utils\DateTimeRange;
use Asterisk\CdrDbBundle\Entity\Cdr;
use Asterisk\CdrDbBundle\Entity\Repositories\CdrRepository;
use Asterisk\DbBundle\Entity\Device;
use Asterisk\DbBundle\Entity\Repositories\FeatureCodeRepository;
use Asterisk\DbBundle\Entity\Repositories\QueueDetailRepository;
use Asterisk\DbBundle\Enums\QueueDetailKeyword;
class Agent
{
public $name;
public $extension;
/** #var Call[] */
public $calls = [];
/** #var array|Queue[] */
public $queues = [];
/** #var AgentStats */
public $stats;
private $_extension;
public function __construct(Device $extension, DateTimeRange $dateTimeRange = null)
{
$this->_extension = $extension;
$this->extension = Extension::createFromDevice($extension);
$this->name = $this->extension->displayName;
$this->calls = $this->getCalls($dateTimeRange);
$this->stats = new AgentStats($this, $dateTimeRange);
}
public function getCalls(DateTimeRange $dateTimeRange = null)
{
/** #var CdrRepository $cdrRepo */
$cdrRepo = CdrRepository::getPreBuiltQueryBuilder();
$query = $cdrRepo->excludeNoAnswer($cdrRepo->filterByDateTimeRange($dateTimeRange));
$cdrs = $query->andWhere(
$query->expr()->orX(
$query->expr()->eq('src', $this->extension->number),
$query->expr()->eq('dst', $this->extension->number)
)
)->andWhere(
$query->expr()->notLike('dst', '*%')
)
->getQuery()->execute();
foreach($cdrs as $cdr) {
$this->calls[] = new Call($cdr);
}
return $this->calls;
}
public function getBusyRange(DateTimeRange $dateTimeRange = null)
{
$on = FeatureCodeRepository::getDndActivate();
$off = FeatureCodeRepository::getDndDeactivate();
$toggle = FeatureCodeRepository::getDndToggle();
$query = CdrRepository::filterByDateTimeRange($dateTimeRange);
/** #var Cdr[] $dndCdrs */
$dndCdrs = $query->where(
$query->expr()->in('dst', [$on, $off, $toggle])
)
->where(
$query->expr()->eq('src', $this->extension->number)
)->getQuery()->execute();
$totalTimeBusy = 0;
/** #var \DateTime $lastMarkedBusy */
$lastMarkedBusy = null;
foreach($dndCdrs as $cdr) {
switch($cdr->getDst())
{
case $on:
$lastMarkedBusy = $cdr->getDateTime();
break;
case $off:
if($lastMarkedBusy != null)
$totalTimeBusy += $lastMarkedBusy->diff($cdr->getDateTime());
$lastMarkedBusy = null;
break;
case $toggle:
if($lastMarkedBusy == null) {
$lastMarkedBusy = $cdr->getDateTime();
}
else
{
$totalTimeBusy += $lastMarkedBusy->diff($cdr->getDateTime());
$lastMarkedBusy = null;
}
break;
}
}
return $totalTimeBusy;
}
public function getQueues()
{
$query = QueueDetailRepository::getPreBuiltQueryBuilder();
$queues = $query->where(
$query->expr()->eq('keyword', QueueDetailKeyword::Member)
)->where(
$query->expr()->like('data', 'Local/'.$this->extension->number.'%')
)->getQuery()->execute();
foreach($queues as $queue)
$this->queues[] = Queue::createFromQueueConfig(QueueDetailRepository::findByColumn('extension', $queue->id), $queue);
return $this->queues;
}
}
EDIT 2:
Actually I forgot I declared each repository as a service, so I could omit the black magic voodoo in the getInstance() method. But loading the service on kernel event seems like a bad idea...
parameters:
entity.device: Asterisk\DbBundle\Entity\Device
services:
asterisk.repository.device:
class: Asterisk\DbBundle\Entity\Repositories\DeviceRepository
factory: ["#doctrine.orm.asterisk_entity_manager", getRepository]
arguments:
- %entity.device%
tags:
- {name: kernel.event_listener, event: kernel.request, method: onKernelRequest}
Edit 3
Cerad gave me an answer on my other related question That suggested using a single kernel event listener service and injecting each repository as a dependency. Thus allowing me to access the repositories statically. My only concern is the overhead required to load each repository on every request. My ideal method would be lazy load the repositories, but I'm unaware of a method at this time. proxy-manager-bridge looked promising but with my singleton pattern I don't think it will work.
I'm developing an Apigility driven application based on the Zend Framework 2.
Currently I'm sending the data retrieved in the database directly to the client: a request comes in, the MyResource#fetch(...) or MyResource#fetchAll(...) gets triggered and calls an appropriate method on MyService class, that calls MyMapper to retireve the data with its methods like findFooByBar(...).
Now I'd like to process the data, before the response is sent. How can I do that?
The Apigility ZF HAL documentation shows, how to access the entity data between it has been retrieved and sent to the client. Well I tried this out. It's ugly and to much code for such task. And... it doesn't work. I want however post here my attept:
namespace Portfolio;
...
class Module implements ApigilityProviderInterface {
private $serviceManager;
public function onBootstrap(MvcEvent $event) {
$application = $event->getTarget();
$this->serviceManager = $serviceManager = $application->getServiceManager();
$viewHelperManager = $serviceManager->get('ViewHelperManager');
$hal = $viewHelperManager->get('Hal');
$hal->getEventManager()->attach('renderEntity', array($this, 'onRenderEntity'));
$hal->getEventManager()->attach('renderCollection', array($this, 'onRenderCollection'));
}
public function onRenderEntity($event) {
$entity = $event->getParam('entity');
if ($entity->entity instanceof ProjectEntity) {
$projectEntity = $entity->entity;
$imageCollection = $this->tempCreateimagesForProject(
$event, $entity->entity->getId()
);
$projectEntity->setImages($imageCollection);
$event->setParam('entity', $projectEntity);
}
}
public function onRenderCollection($event) {
$collection = $event->getParam('collection');
$projectCollection = $collection->getCollection();
if ($projectCollection instanceof ProjectCollection) {
foreach ($projectCollection as $key => $projectItem) {
$tempProject = $projectCollection->getItem($key);
$tempProject->append(
['images' => $this->tempCreateimagesForProject($tempProject->offsetGet('id'))]
);
$projectCollection->getItem($key)->offsetSet($key, $tempProject);
}
}
}
private function tempCreateimagesForProject(Event $event, $projectId) {
$imageService = $this->serviceManager->get('Portfolio\V2\Rest\ImageService');
$imageCollection = $imageService->getImagesForProject($projectId);
return $imageCollection;
}
...
}
I think using the renderEntity and renderCollection events is not the correct spot to add this kind of resource specific logic. It is more suitable for more general changes or incidental customization.
You can add this logic to your resource listeners. So in your fetch and fetchAll methods in your MyResource class you can add the custom code you currently added in these onRenderEntity and onRenderCollection methods.
So something like this:
class MyResource extends AbstractResourceListener
{
/**
* Your image service dependency
*/
protected $imageService;
/* ... */
public function fetch($id)
{
$project = $this->projectMapper->fetch($id);
$imageCollection = $this->imageService->getImagesForProject($project);
$project->setImages($imageCollection);
return $project;
}
/* ... */
public function fetchAll($params = array())
{
$projects = $this->projectMapper->fetchAll();
foreach ($projects as $key => $project) {
$imageCollection = $this->imageService->getImagesForProject($project);
$project->setImages($imageCollection);
}
return $projects;
}
/* ... */
}
One possible solution is handling the data in the Hydrator. So we write a custom Hydrator class and enrich the items with nested objects and lists in it. It can look like this:
Portfolio\V2\Rest\Project\ProjectHydrator
...
class ProjectHydrator extends ClassMethods {
/**
* #var ImageService
*/
protected $imageService;
...
/*
* Doesn't need to be implemented:
* the ClassMethods#hydrate(...) handle the $data already as wished.
*/
/*
public function hydrate(array $data, $object) {
$object = parent::hydrate($data, $object);
if ($object->getId() !== null) {
$images = $this->imageService->getImagesForProject($object->getId());
$object->setImages($images);
}
return $object;
}
*/
/**
* #see \Zend\Stdlib\Hydrator\ClassMethods::extract()
*/
public function extract($object) {
$array = parent::extract($object);
if ($array['id'] !== null) {
$images = $this->imageService->getImagesForProject($array['id']);
$array['images'] = $images;
}
return $array;
}
}
It's not a nice solution, since then a part of the model / data retrieving logic gets moved to the hydrator. But it works. Here is shown an implementation of this approach and here is a discussion to this topic on GitHub.
If you are using the ClassMethods Hydrator and your Collection extends \Zend\Paginator\Paginator a good solution without losing the Collection's consistency and not changing anybody's code is to overwrite your getCurrentItems() method.
public class MyResourceCollection // most likely extends Paginator
{
public function getCurrentItems()
{
// getting the original items
$items = parent::getCurrentItems();
// as we work with objects $item will be an object
// when working with objects we use references to them not clones of objects
// so changes to $item are also present in the collection
foreach ($collection as $item) {
$oldSomething = $item->getSomething();
$item->setSomething('['.$oldSomething.']');
}
// $items are now changed, return them
return $items;
}
}
I have named the key something not to get confused with the getValue method from other places.
This makes the something value look like [something].
In my project i use following code to access session variables via Session Bags in services:
public function __construct()
{
// Create session bag
$className = get_class($this);
$this->storage = new Phalcon\Session\Bag($className);
}
But this gives an exception "A dependency injection object is required to access the 'session' service".
Ok, it seems that we need to setup a DI here. Most simple way - to define not shared sessionBag service in DI ($di will be set automatically then). But how can i understand which name i should setup for Session bag this way? Example:
$di->set('sessionBag', function() use ($config) {
$name = ''; // ???
$bag = new \Phalcon\Session\Bag($name);
return $bag;
});
You can make your class inherit from Phalcon\DI\Injectable, a session bag is implicitly created when you access the persistent property:
class MyComponent extends Phalcon\DI\Injectable
{
public function someMethod()
{
$this->persistent->someName = "peter";
}
}
//Start the session the first time when some component request
// the session service
$di->setShared('session', function() {
$session = new Phalcon\Session\Adapter\Files();
$session->start();
return $session;
});
Example:
auth.php:
public function __construct(){
$this->_di = \Phalcon\DI::getDefault ();
$this->user = new \Phalcon\Session\Bag(get_class());
$this->user->setDI($this->_di);
}
/**
*
* #param int
*
* #return bool
*/
public function authenticate($identity){
$this->user->identity=$identity;
}
/**
* #return boolean
*/
public function isAuthenticate(){
return $this->user->identity?true:false;
}