Doctrine annotation loader fails - php

I'm trying to run JMSSerializer. My simple code
use JMS\Serializer\Annotation\Type;
class Comment
{
private $msg;
public function __construct($msg)
{
$this->msg = $msg;
}
}
class Person
{
/**
* #Type("array<Comment>")
*/
private $commentList;
public function addComment(Comment $comment)
{
$this->commentList[] = $comment;
}
}
$type = new Type;
$serializer = JMS\Serializer\SerializerBuilder::create()->build();
$data = new Person();
$data->addComment(new Comment('hey'));
var_dump($serializer->serialize($data, 'json'));
fails with
PHP Fatal error: Uncaught exception 'Doctrine\Common\Annotations\AnnotationException' with message '[Semantical Error] The annotation "#JMS\Serializer\Annotation\Type" in property Person::$commentList does not exist, or could not be auto-loaded.' in xxx.php:52
OK, but if I add line
$type = new Type;
to trigger autoloader manually, it works:
string(32) "{"comment_list":[{"msg":"hey"}]}"
As I see AnnotationRegistry doesn't use autoloader, it tries to use some own autoloader. It looks ugly, what do I have to do to fix it?

OK, I answer my question myself. I have to register annotations somewhere in autoloader file:
\Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace(
'JMS\Serializer\Annotation', __DIR__.'/vendor/jms/serializer/src'
);
Other ways: http://docs.doctrine-project.org/projects/doctrine-common/en/latest/reference/annotations.html#registering-annotations

A full configuration sample for the standalone JMS serializer library could be:
<?php
namespace iMSCP\Service;
use JMS\Serializer\Serializer;
use JMS\Serializer\SerializerBuilder;
use Doctrine\Common\Annotations\AnnotationRegistry;
use iMSCP_Registry as Registry;
/**
* Class SerializerServiceFactory
* #package iMSCP\Service
*/
class SerializerServiceFactory
{
/**
* #var Serializer
*/
static $serialiszer;
public static function create()
{
if (static::$serialiszer === null) {
$config = Registry::get('config');
AnnotationRegistry::registerAutoloadNamespace(
'JMS\Serializer\Annotation', $config['CACHE_DATA_DIR'] . '/packages/vendor/jms/serializer/src'
);
static::$serialiszer = SerializerBuilder::create()
->setCacheDir(CACHE_PATH . '/serializer')
->setDebug($config['DEVMODE'])
->build();
}
return static::$serialiszer;
}
}
Here, I register the JMS\Serializer\Annotation namespace using the Annotation registry as provided by Doctrine. Once done, all is working as expected.

Related

amphp auto-load class not working as expected

I'm trying to use a custom class in a worker using amphp but it doesn't seem to be working. The below class is already auto-loaded using composer. Please help me out with this issue. My code is below:
Class (this implements Task as mentioned on their docs):
<?php
namespace Jobs;
require '/home/xxx/vendor/autoload.php';
use Amp\Parallel\Worker\Environment;
use Amp\Parallel\Worker\Task;
class GetMarketJob implements Task {
/**
* #var callable
*/
private $function;
/**
* #var mixed[]
*/
private $args;
public function __construct($function, ...$args) {
$this->function = $function;
$this->args = $args;
}
/**
* {#inheritdoc}
*/
public function run(Environment $environment)
{
if ($this->function instanceof \__PHP_Incomplete_Class) {
throw new \Error('When using a class instance as a callable, the class must be autoloadable');
}
if (\is_array($this->callable) && ($this->callable[0] ?? null) instanceof \__PHP_Incomplete_Class) {
throw new \Error('When using a class instance method as a callable, the class must be autoloadable');
}
if (!\is_callable($this->function)) {
$message = 'User-defined functions must be autoloadable (that is, defined in a file autoloaded by composer)';
if (\is_string($this->function)) {
$message .= \sprintf("; unable to load function '%s'", $this->function);
}
throw new \Error($message);
}
return ($this->function)(...$this->args);
}
public function testMe($url = NULL) {
$test = file_get_contents($url);
return $test;
}
}
File using amphp to assign worker using above class:
<?php
require '/home/xxxx/vendor/autoload.php';
use Jobs\GetMarketJob;
// Example async producer using promisor
use Amp\Parallel\Worker;
use Amp\Promise;
use Amp\Loop;
use Amp\Parallel\Worker\DefaultPool;
use Amp\Parallel\Worker\Task;
use Amp\Parallel\Worker\Environment;
use Amp\Parallel\Worker\TaskFailureError;
use Amp\Parallel\Worker\DefaultWorkerFactory;
Amp\Loop::run(function () {
$factory = new DefaultWorkerFactory();
$worker = $factory->create();
$result = yield $worker->enqueue(new GetMarketJob('testMe', ['https://www.syhtek.com']));
print($result);
$code = yield $worker->shutdown();
\printf("Code: %d\n", $code);
});
running this script gives me the below output:
[26-May-2021 01:23:11 UTC] PHP Fatal error: Uncaught
Amp\Parallel\Worker\TaskFailureError: Uncaught Error in worker with
message "User-defined functions must be autoloadable (that is, defined
in a file autoloaded by composer); unable to load function 'testMe'"
and code "0"; use
Amp\Parallel\Worker\TaskFailureError::getOriginalTrace() for the stack
trace in the worker in
/home/xxxx/vendor/amphp/parallel/lib/Worker/Internal/TaskFailure.php:60
Thank you so much for reading!
The issue here is that you're passing 'testMe' and then check if (!\is_callable($this->function)) {, while it should be if (!\method_exists($this, $this->function)) {.
And return ($this->function)(...$this->args); should be return ($this->{$this->function})(...$this->args); if you're trying to call that method. You might also call that method directly instead of giving it to the constructor.
If everything you do in the worker is an HTTP request, you should look into amphp/http-client instead of amphp/parallel, as non-blocking I/O is much more efficient than several chlid processes with blocking I/O.

Ignore a class in a autowired directory

I have an Exception somewhere in my service/ folder, and Symfony is trying to autowire it :
Cannot autowire service
"App\Service\Order\Exception\StripeRequiresActionException": argument
"$secretKey" of method "__construct()" is type-hinted "string", you
should configure its value explicitly.
This is my class :
class StripeRequiresActionException extends \Exception
{
/**
* #var string
*/
protected $secretKey;
public function __construct(string $secretKey)
{
parent::__construct();
$this->secretKey = $secretKey;
}
/**
* #return string
*/
public function getSecretKey(): string
{
return $this->secretKey;
}
}
I don't want it to be autowired. Is there an easy way to prevent this class to be loaded by the DI, with an annotation for example? I know I can exclude this class in my yaml configuration, but I don't want to do that because I find this ugly and harder to maintain.
Maybe you could exclude all exceptions, no matter where they are.
If all your exceptions follow the pattern you show in your question, you could do something similar to:
App\:
resource: '../src/*'
exclude: ['../src/{Infrastructure/Symfony,Domain,Tests}', '../src/**/*Exception.php']
This comes directly from a project I have open right here. The default exclude for Symfony looks somewhat different. But the important bit would be to add the pattern *Exception.php to the excluded files.
This is simpler to maintain than an annotation, even if an annotation were possible (which I believe it's not). Keeps the configuration all in a same place, you can create new exceptions without having to change configuration or add unnecessary code.
Even if I agree that in your particular case the cleanest way is to do what yivi suggested, I think I have a more generic solution that could suit more cases.
In my case I have a PagesScanner service that returns PageResult objects, both are several level deep into an autowired directory.
Excluding the class like suggested is a pain and will make the yaml unreadable quickly as the number of exceptions increases.
So I created a new compiler pass that searches for an #IgnoreAutowire annotation on each class under the App/ folder :
<?php
namespace App\DependencyInjection\Compiler;
use App\Annotation\IgnoreAutowire;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
final class RemoveUnwantedAutoWiredServicesPass implements CompilerPassInterface
{
/**
* {#inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$annotationReader = new AnnotationReader();
$definitions = $container->getDefinitions();
foreach ($definitions as $fqcn => $definition) {
if (substr($fqcn, 0, 4) === 'App\\') {
try {
$refl = new \ReflectionClass($fqcn);
$result = $annotationReader->getClassAnnotation($refl, IgnoreAutowire::class);
if ($result !== null) {
$container->removeDefinition($fqcn);
}
} catch (\Exception $e) {
// Ignore
}
}
}
}
}
This way all I have to do is to add the annotation to classes I don't want to be autowired:
<?php
namespace App\Utils\Cms\PagesFinder;
use App\Annotation\IgnoreAutowire;
/**
* #IgnoreAutowire()
*/
class PageResult
{
[...]
}
Another good thing about this approch is you can even have parameters in the class constructor without any error because the actual autowiring thing is done after the compiler pass.
BTW code for php8 attributes:
CompilerPass
<?php
namespace App\DependencyInjection\Compiler;
use App\Annotation\IgnoreAutowire;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
final class RemoveUnwantedAutoWiredServicesPass implements CompilerPassInterface
{
/**
* {#inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$definitions = $container->getDefinitions();
foreach ($definitions as $fqcn => $definition) {
if (str_starts_with($fqcn, 'App\\')) {
try {
$refl = new \ReflectionClass($fqcn);
$attribute = $refl->getAttributes(IgnoreAutowire::class)[0] ?? null;
if ($attribute !== null) {
$container->removeDefinition($fqcn);
}
} catch (\Exception $e) {
// Ignore
}
}
}
}
}
Attribute
<?php
namespace App\Annotation;
use Attribute;
/**
* Annotation class for #IgnoreAutowire().
*
* #Annotation
* #Target({"CLASS"})
*/
#[Attribute(Attribute::TARGET_CLASS)]
class IgnoreAutowire
{
}
You can also disable autoconfigure for only this class in config/services.yaml:
App\Service\Order\Exception\StripeRequiresActionException:
autoconfigure: false
Symfony won't add this class to the DI.

Symfony - Wrapper/Helper classes having access to entity's repository

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.

Troubles with GO! Aspect-Oriented Framework

If someone work with GO! framework, can you help me.
I install framework on php 5.3.13. Demo example is working.
But my own example doesn't work. Aspect(method beforeMethodExecution) is not perfomed.
Here is my code.
Main file:
//1 Include kernel and all classes
if (file_exists(__DIR__ .'/../../vendor/autoload.php')) {
$loader = include __DIR__ .'/../../vendor/autoload.php';
}
// 2 Make own ascpect kernel
use Go\Core\AspectKernel;
use Go\Core\AspectContainer;
class Kernel extends AspectKernel{
/**
* Configure an AspectContainer with advisors, aspects and pointcuts
*
* #param AspectContainer $container
*
* #return void
*/
public function configureAop(AspectContainer $container)
{
}
}
//3 Initiate aspect kernel
$Kernel = Kernel::getInstance();
$Kernel->init();
//4 Include aspect
include(__DIR__.'/aspectclass/AspectClass.php');
$aspect = new DebugAspect();
//5 register aspect
$Kernel->getContainer()->registerAspect($aspect);
//6 Include test class
include(__DIR__.'/class/class1.php');
//7 Execute test class
$Class = new General('test');
$Class->publicHello();
File with test class:
class General{
protected $message = '';
public function __construct($message)
{
$this->message = $message;
}
public function publicHello()
{
echo 'Hello, you have a public message: ', $this->message, "<br>", PHP_EOL;
}
}
File with aspect:
use Go\Aop\Aspect;
use Go\Aop\Intercept\FieldAccess;
use Go\Aop\Intercept\FunctionInvocation;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\After;
use Go\Lang\Annotation\Before;
use Go\Lang\Annotation\Around;
use Go\Lang\Annotation\Pointcut;
use Go\Lang\Annotation\DeclareParents;
use Go\Lang\Annotation\DeclareError;
class DebugAspect implements Aspect{
/**
* Method that should be called before real method
*
* #param MethodInvocation $invocation Invocation
* #Before("execution(General->*(*))")
*
*/
public function beforeMethodExecution(MethodInvocation $invocation)
{
$obj = $invocation->getThis();
echo 'Calling Before Interceptor for method: ',
is_object($obj) ? get_class($obj) : $obj,
$invocation->getMethod()->isStatic() ? '::' : '->',
$invocation->getMethod()->getName(),
'()',
' with arguments: ',
json_encode($invocation->getArguments()),
PHP_EOL;
}
}
As you know, go-aop isn't a PHP extension, so it couldn't transform classes that were loaded directly via require or include. Internally it tries to overwrite the source code on-the-fly, but it should receive a control (via integration with composer or custom autoloader class).
So, you have an error here:
//6 Include test class
include(__DIR__.'/class/class1.php');
You explicitly load this class into memory and there is no way to transform it from userland. To pass a control to the framework, you should make this explicitly. Look at the line AopComposerLoader.php#L99 to have an idea how it works. Here we include a source file via the stream source filter that pass control to the framework and it can transform the class to weave an aspects.
To fix your example just change an include to the following:
include (FilterInjectorTransformer::rewrite(__DIR__.'/class/class1.php'));

Laravel4, access instance methods within a Facade implementation ends up in PHP fatal: call to undefined method

This is a test i am running to understand how framework works. I've coded a trivial facade implementation, a service provider and i properly registered it. Code:
// This is the facade
<?php
namespace my\facades;
use Illuminate\Support\Facades\Facade;
class DateHelper extends Facade {
/**
* Get the registered name of the component.
* #return string
*/
protected static function getFacadeAccessor() {
// Return the instance key name in the IoC Container.
return 'my.utils.dateHelper';
}
}
// This is the service provider
<?php
namespace my\providers;
class MyServiceProvider extends \Illuminate\Support\ServiceProvider {
/**
* Register the service provider.
*/
public function register() {
// Register the date handler
$this->app['my.utils.dateHelper'] = $this->app->share(function($app) {
return new \my\utils\DateHelper();
});
// Shortcut so developers don't need to add an Alias in app/config/app.php
$this->app->booting(function(){
$loader = \Illuminate\Foundation\AliasLoader::getInstance();
$loader->alias('DateHelper', '\my\utils\DateHelper');
});
}
}
// This is the actual implementation
class DateHelper {
public static $UI_DATE_FORMAT = 'd/m/y';
public static $UI_DATETIME_FORMAT = 'd/m/y H:i:s';
/**
*
* Enter description here ...
* #param DateTime $date0
* #param DateTime $date1
*/
public function compare(mixed $date0, mixed $date1) {
if (is_string($date0)) {
$date0 = $this->a($date0)->getTimestamp();
}
if (is_string($date1)) {
$date1 = $this->a($date1)->getTimestamp();
}
return $date0 < $date1 ? -1 : $date1 > $date0 ? 1 : 0;
}
/**
*
* Enter description here ...
* #param string $inputDate
*/
public function a( $inputDate) {
return \DateTime::createFromFormat(self::$UI_DATE_FORMAT, $inputDate);
}
}
Now, i.e. from a Controller, i am able to
invoke DateHelper::compare("one", "two");
invoke DateHelper::a(1);
Methods are properly resolved. What is not working is the line of the sort
$date0 = $this->a($date0)->getTimestamp();
where i get a PHP fatal stating that
PHP Fatal error: Call to undefined method DateHelperTest::a()
I think this has something to do with magic methods. I tried to invoke them
statically, but nothing changes. How i can i invoke
a()
from the
compare()
body?
== EDIT ==
I am writing a test against this facade, the like of this
<?php
class DateHelperTest extends \TestCase {
public function testComparison() {
// Compare strings
$d0 = "01/01/2012";
$d1 = "02/01/2012";
$this->assertEquals(DateHelper::compare($d0, $d1), 1);
$this->assertEquals(DateHelper::compare($d1, $d0), -1);
$this->assertEquals(DateHelper::compare($d0, $d0), 0);
// Compare DateTimes
$dateTime0 = new DateTime($d0);
$dateTime1 = new DateTime($d1);
$this->assertEquals(DateHelper::compare($dateTime0, $dateTime1), 1);
$this->assertEquals(DateHelper::compare($dateTime1, $$dateTime0), -1);
$this->assertEquals(DateHelper::compare($dateTime0, $dateTime1), 0);
}
public function testParser() {
$sample = "01/02/2012";
$d = DateHelper::parseInput($sample);
$this->assertEquals($d->format("d/m/Y"), $sample);
$this->assertEquals($d->format("d"), "01");
$this->assertEquals($d->format("m"), "02");
$this->assertEquals($d->format("Y"), "2012");
}
}
I just spotted a weird thing i didn't see at first. Error states that
PHP Fatal error: Call to undefined method
DateHelperTest::parseInput() in
/var/www/my/app/my/utils/DateHelper.php on line 15
so, what's this? the $this->fooBar method in DateHelper is resolved to DateHelperTest->foobar()?
I haven't seen the AliasLoader used in that way before, but I think your problem might be there. The alias should point to the Facade, not to the actual class.
(I'd also encourage you to rename your facade to make it clearer which class is the facade and which is the underlying class -- I'd bet you're going to burn yourself at least once by using the wrong namespace and getting a class you don't expect.)

Categories