A better way to instantiate a PHP class - php

I have a class with only one function:
<?php
class EventLog
{
public function logEvent($data, $object, $operation, $id)
{
//Log it to a file...
$currentTime = new DateTime();
$time = $currentTime->format('Y-m-d H:i:s');
$logFile = "/.../event_log.txt";
$message = "Hello world";
//Send the data to a file...
file_put_contents($logFile, $message, FILE_APPEND);
}
}
Then I have another class with many functions and each and everyone need to call the above method. To instantiate the class in every function I have done:
$log = new EventLog();
//Then...
$log->logEvent($data, $object, $operation, $id);
The problem: I have used the above code in every function and what I would like to know is if there is a way to instantiate the EventLog class once for all the functions that need it.

You can create single instance at the beginning(for example) of your script and inject it into constructors of those classes that need it. This is called Dependency Injection. Most PHP web frameworks utilize this principle.
class Logger
{
public function writeToLogFile(){
...
}
}
class DoSomethingUseful
{
private $logger;
public function __construct(Logger $logger) //php 7 optional type hinting
{
$this->logger = $logger;
}
public function actualWork()
{
//do work
$this->logger->writeToLogFile('whatever');
}
}
class Application
{
public function setUp()
{
//create database connection, other stuff
$this->logger = new Logger;
}
public function work()
{
$action = new DoSomethingUseful($this->logger);
$action->actualWork();
}
}

You could also try using PHP Trait (with namespacing):
<?php
namespace App\Traits;
trait EventLog
{
public function logEvent($data, $object, $operation, $id)
{
//Log it to a file...
$currentTime = new DateTime();
$time = $currentTime->format('Y-m-d H:i:s');
$logFile = "/.../event_log.txt";
$message = "Hello world";
//Send the data to a file...
file_put_contents($logFile, $message, FILE_APPEND);
}
}
In your other class:
<?php
namespace App;
// import your trait here
use App\Traits\EventLog;
class OtherClass
{
use EventLog;
public function sample() {
// sample call to log event
$this->logEvent($data, $object, $operation, $id);
}
}

Related

PHPUnit mock method from another class

With PHPUnit 6.5.14, I am trying to test a method. To do this, one of its dependencies needs to be mocked; however I can't get it working. Here is a stripped down version:
class Order {
public function create() {
$CCP = new CreditCardProcessor();
$success = $CCP->chargeCreditCard();
return $success;
}
}
class CreditCardProcessor {
public function chargeCreditCard() {
return false;
}
}
class OrderTest extends TestCase {
public function testCreate() {
$mockCCP = $this->getMockBuilder(CreditCardProcessor::class)
->setMethods(['chargeCreditCard'])
->getMock();
$mockCCP
->method('chargeCreditCard')
->willReturn(true);
$O = new Order();
$success = $O->create();
$this->assertTrue($success, 'Was not able to create order.');
}
}
I've read the docs and gone over some examples but can't figure it out. Any ideas what I am doing wrong? Thanks.
After finding more examples, I believe the solution is to pass in the dependency to create as an argument:
class Order {
public function create($CCP) {
$success = $CCP->chargeCreditCard();
return $success;
}
}
Then I can do the same in my test:
class OrderTest extends TestCase {
public function testCreate() {
$mockCCP = $this->getMockBuilder(CreditCardProcessor::class)
->setMethods(['chargeCreditCard'])
->getMock();
$mockCCP
->method('chargeCreditCard')
->willReturn(true);
$O = new Order();
$success = $O->create($mockCCP);
$this->assertTrue($success, 'Was not able to create order.');
}
}
I haven't tried yet, but that should do it.
I don't like the idea of changing code to satisfy tests, but it probably is also an indication I need to restructure my code.

PHP OOP: Create global array of messages

I am trying to display an array of messages at the end of my PHP class. My message handler is working, but only if I "add_message" from within the main parent class and not if I call this function from within a child class. Sorry if this is vague but was not sure how to word the question.
TLDR; How can I add a message from within class Example?
MAIN PARENT CLASS
class Init {
public function __construct() {
$this->load_dependencies();
$this->add_messages();
$this->add_msg_from_instance();
}
private function load_dependencies() {
require_once ROOT . 'classes/class-messages.php';
require_once ROOT . 'classes/class-example.php';
}
public function add_messages() {
$this->messages = new Message_Handler();
$this->messages->add_message( 'hello world' );
}
// I Would like to add a message from within this instance....
public function add_msg_from_instance() {
$example = new Example();
$example->fire_instance();
}
public function run() {
$this->messages->display_messages();
}
}
MESSAGE HANDLER
class Message_Handler {
public function __construct() {
$this->messages = array();
}
public function add_message( $msg ) {
$this->messages = $this->add( $this->messages, $msg );
}
private function add( $messages, $msg ) {
$messages[] = $msg;
return $messages;
}
// Final Function - Should display array of all messages
public function display_messages() {
var_dump( $this->messages );
}
}
EXAMPLE CLASS
class Example {
public function fire_instance() {
$this->messages = new Message_Handler();
$this->messages->add_message( 'Hello Universe!' ); // This message is NOT being displayed...
}
}
Because you want to keep the messages around different object, you should pass the object or use a static variable.
I would use a static variable like so:
class Init {
public function __construct() {
$this->load_dependencies();
$this->add_messages();
$this->add_msg_from_instance();
}
private function load_dependencies() {
require_once ROOT . 'classes/class-messages.php';
require_once ROOT . 'classes/class-example.php';
}
public function add_messages() {
// renamed the message handler variable for clarity
$this->message_handler = new Message_Handler();
$this->message_handler->add_message( 'hello world' );
}
// I Would like to add a message from within this instance....
public function add_msg_from_instance() {
$example = new Example();
$example->fire_instance();
}
public function run() {
$this->message_handler->display_messages();
}
}
class Message_Handler {
// use a static var to remember the messages over all objects
public static $_messages = array();
// add message to static
public function add_message( $msg ) {
self::$_messages[] = $msg;
}
// Final Function - Should display array of all messages
public function display_messages() {
var_dump( self::$_messages );
}
}
class Example {
public function fire_instance() {
// new object, same static array
$message_handler = new Message_Handler();
$message_handler->add_message( 'Hello Universe!' );
}
}
// testing...
new Init();
new Init();
$init = new Init();
$init->add_msg_from_instance();
$init->add_msg_from_instance();
$init->add_msg_from_instance();
$init->run();
Although global variables might not be the best design decision, you have at least two approaches to achieve what you want:
Use singleton.
Nowadays it is considered anti-pattern, but it is the simplest way: make message handler a singleton:
class MessageHandler
{
private static $instance;
private $messages = [];
public static function instance(): self
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct()
{
}
public function addMessage($message): self
{
$this->messages[] = $message;
return $this;
}
public function messages(): array
{
return $this->messages;
}
}
Then instead of creating a new instance of MessageHandler access it via the static method MessageHandler::instance(). Here is a demo.
Use DI container to inject the same instance (that is created once and held in the container) into all instances that need to access it. This approach is more preferable, but harder to implement in the project where there is no DI container available in the first place.

Laravel Controller use same object in two functions

Is there a way to use a object variable instantiated from a class in two functions?
Here's the code I've tried, but its just returning null:
class bookAppointmentsController extends APIController
{
private $business;
public funcition check($key)
{
$this->business = new APIClass();
$setconnection = $this->business->connectAPI($key);
}
public function book()
{
dd($this->business) //returns null
$this->business->book();
}
}
I am trying to use the $business object in two functions but it does not work, when I dd($business) it returns null
Any way to do this?
Move the instantiation to the constructor:
public function __construct(APIClass $business)
{
$this->business = $business;
}
However, it would be better if you make Laravel do the heavy lifting and prepare the APIClass for you.
In your AppServicePorvider under the register method, you can create the APIClass
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind('APIClass', function ($app) {
$api = new APIClass();
// Do any logic required to prepare and check the api
$key = config('API_KEY');
$api->connectAPI($key);
return $api;
});
}
Check the documentations for more details.
Maybe the solution could be to make the variable Global
You could make the variable global:
function method( $args ) {
global $newVar;
$newVar = "Something";
}
function second_method() {
global $newVar;
echo $newVar;
}
Or you could return it from the first method and use it in the second method
public function check($key)
{
$this->business = new APIClass();
$setconnection = $this->business->connectAPI($key);
return $this->business;
}
public function book()
{
$business = check($key);
$business->book();
}

How to set-up a generic leveraged logging using Monolog?

I am writting a console application with Symfony2 components, and I want to add distinct logging channels for my services, my commands and so on. The problem: to create a new channel requires to create a new instance of Monolog, and I don't really know how to handle this in a generic way, and without needing to pass the stream handler, a channel and the proper code to bind the one and the other inside all services.
I did the trick using debug_backtrace():
public function log($level, $message, array $context = array ())
{
$trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), 1);
$caller = $trace[0]['class'] !== __CLASS__ ? $trace[0]['class'] : $trace[1]['class'];
if (!array_key_exists($caller, $this->loggers))
{
$monolog = new Monolog($caller);
$monolog->pushHandler($this->stream);
$this->loggers[$caller] = $monolog;
}
$this->loggers[$caller]->log($level, $message, $context);
}
Whatever from where I call my logger, it creates a channel for each class that called it. Looks cool, but as soon as a logger is called tons of time, this is performance-killing.
So here is my question:
Do you know a better generic way to create one distinct monolog channel per class that have a logger property?
The above code packaged for testing:
composer.json
{
"require" : {
"monolog/monolog": "~1.11.0"
}
}
test.php
<?php
require('vendor/autoload.php');
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
class Test
{
public function __construct($logger)
{
$logger->info("test!");
}
}
class Hello
{
public function __construct($logger)
{
$logger->log(Monolog\Logger::ALERT, "hello!");
}
}
class LeveragedLogger implements \Psr\Log\LoggerInterface
{
protected $loggers;
protected $stream;
public function __construct($file, $logLevel)
{
$this->loggers = array ();
$this->stream = new StreamHandler($file, $logLevel);
}
public function alert($message, array $context = array ())
{
$this->log(Logger::ALERT, $message, $context);
}
public function critical($message, array $context = array ())
{
$this->log(Logger::CRITICAL, $message, $context);
}
public function debug($message, array $context = array ())
{
$this->log(Logger::DEBUG, $message, $context);
}
public function emergency($message, array $context = array ())
{
$this->log(Logger::EMERGENCY, $message, $context);
}
public function error($message, array $context = array ())
{
$this->log(Logger::ERROR, $message, $context);
}
public function info($message, array $context = array ())
{
$this->log(Logger::INFO, $message, $context);
}
public function log($level, $message, array $context = array ())
{
$trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), 1);
$caller = $trace[0]['class'] !== __CLASS__ ? $trace[0]['class'] : $trace[1]['class'];
if (!array_key_exists($caller, $this->loggers))
{
$monolog = new Logger($caller);
$monolog->pushHandler($this->stream);
$this->loggers[$caller] = $monolog;
}
$this->loggers[$caller]->log($level, $message, $context);
}
public function notice($message, array $context = array ())
{
$this->log(Logger::NOTICE, $message, $context);
}
public function warning($message, array $context = array ())
{
$this->log(Logger::WARNING, $message, $context);
}
}
$logger = new LeveragedLogger('php://stdout', Logger::DEBUG);
new Test($logger);
new Hello($logger);
Usage
ninsuo:test3 alain$ php test.php
[2014-10-21 08:59:04] Test.INFO: test! [] []
[2014-10-21 08:59:04] Hello.ALERT: hello! [] []
What would you think about making the decision which logger should be used right before the consumers are created? This could be easily accomplished with some kind of DIC or maybe a factory.
<?php
require('vendor/autoload.php');
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Psr\Log\LoggerInterface;
use Monolog\Handler\HandlerInterface;
class Test
{
public function __construct(LoggerInterface $logger)
{
$logger->info("test!");
}
}
class Hello
{
public function __construct(LoggerInterface $logger)
{
$logger->log(Monolog\Logger::ALERT, "hello!");
}
}
class LeveragedLoggerFactory
{
protected $loggers;
protected $stream;
public function __construct(HandlerInterface $streamHandler)
{
$this->loggers = array();
$this->stream = $streamHandler;
}
public function factory($caller)
{
if (!array_key_exists($caller, $this->loggers)) {
$logger = new Logger($caller);
$logger->pushHandler($this->stream);
$this->loggers[$caller] = $logger;
}
return $this->loggers[$caller];
}
}
$loggerFactory = new LeveragedLoggerFactory(new StreamHandler('php://stdout', Logger::DEBUG));
new Test($loggerFactory->factory(Test::class));
new Hello($loggerFactory->factory(Hello::class));
I finally created a MonologContainer class that extends the standard Symfony2 container, and injects a Logger to LoggerAware services. Overloading the get() method of the service container, I can get the service's ID, and use it as a channel for the logger.
<?php
namespace Fuz\Framework\Core;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Monolog\Handler\HandlerInterface;
use Monolog\Logger;
use Psr\Log\LoggerAwareInterface;
class MonologContainer extends ContainerBuilder
{
protected $loggers = array ();
protected $handlers = array ();
protected $processors = array ();
public function __construct(ParameterBagInterface $parameterBag = null)
{
parent::__construct($parameterBag);
}
public function pushHandler(HandlerInterface $handler)
{
foreach (array_keys($this->loggers) as $key)
{
$this->loggers[$key]->pushHandler($handler);
}
array_unshift($this->handlers, $handler);
return $this;
}
public function popHandler()
{
if (count($this->handlers) > 0)
{
foreach (array_keys($this->loggers) as $key)
{
$this->loggers[$key]->popHandler();
}
array_shift($this->handlers);
}
return $this;
}
public function pushProcessor($callback)
{
foreach (array_keys($this->loggers) as $key)
{
$this->loggers[$key]->pushProcessor($callback);
}
array_unshift($this->processors, $callback);
return $this;
}
public function popProcessor()
{
if (count($this->processors) > 0)
{
foreach (array_keys($this->loggers) as $key)
{
$this->loggers[$key]->popProcessor();
}
array_shift($this->processors);
}
return $this;
}
public function getHandlers()
{
return $this->handlers;
}
public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
{
$service = parent::get($id, $invalidBehavior);
return $this->setLogger($id, $service);
}
public function setLogger($id, $service)
{
if ($service instanceof LoggerAwareInterface)
{
if (!array_key_exists($id, $this->loggers))
{
$this->loggers[$id] = new Logger($id, $this->handlers, $this->processors);
}
$service->setLogger($this->loggers[$id]);
}
return $service;
}
}
Usage example:
test.php
#!/usr/bin/env php
<?php
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Fuz\Framework\Core\MonologContainer;
if (!include __DIR__ . '/vendor/autoload.php')
{
die('You must set up the project dependencies.');
}
$container = new MonologContainer();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.yml');
$handler = new StreamHandler(__DIR__ ."/test.log", Logger::WARNING);
$container->pushHandler($handler);
$container->get('my.service')->hello();
services.yml
parameters:
my.service.class: Fuz\Runner\MyService
services:
my.service:
class: %my.service.class%
MyService.php
<?php
namespace Fuz\Runner;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
class MyService implements LoggerAwareInterface
{
protected $logger;
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function hello()
{
$this->logger->alert("Hello, world!");
}
}
Demo
ninsuo:runner alain$ php test.php
ninsuo:runner alain$ cat test.log
[2014-11-06 08:18:55] my.service.ALERT: Hello, world! [] []
You can try this
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;
class Loggr{
private static $_logger;
public $_instance;
public $_channel;
private function __construct(){
if(!isset(self::$_logger))
self::$_logger = new Logger('Application Log');
}
// Create the logger
public function logError($error){
self::$_logger->pushHandler(new StreamHandler(LOG_PATH . 'application.'. $this->_channel . '.log', Logger::ERROR));
self::$_logger->addError($error);
}
public function logInfo($info){
self::$_logger->pushHandler(new StreamHandler(LOG_PATH . 'application.'. $this->_channel . '.log', Logger::INFO));
self::$_logger->addInfo($info);
}
public static function getInstance($channel) {
$_instance = new Loggr();
$_instance->_channel = strtolower($channel);
return $_instance;
}
}
and can be consumed as
class LeadReport extends Controller{
public function __construct(){
$this->logger = Loggr::getInstance('cron');
$this->logger->logError('Error generating leads');
}
}

PHP : Function argument must be an Object with dynamic class name

so I am new in the world of object oriented programming and I am currently facing this problem (everything is described in the code):
<?php
class MyClass {
// Nothing important here
}
class MyAnotherClass {
protected $className;
public function __construct($className){
$this->className = $className;
}
public function problematicFunction({$this->className} $object){
// So, here I obligatorily want an $object of
// dynamic type/class "$this->className"
// but it don't works like this...
}
}
$object = new MyClass;
$another_object = new MyAnotherClass('MyClass');
$another_object->problematicFunction($object);
?>
Can anyone help me ?
Thanks, Maxime (from France : sorry for my english)
What you need is
public function problematicFunction($object) {
if ($object instanceof $this->className) {
// Do your stuff
} else {
throw new InvalidArgumentException("YOur error Message");
}
}
Try like this
class MyClass {
// Nothing important here
public function test(){
echo 'Test MyClass';
}
}
class MyAnotherClass {
protected $className;
public function __construct($className){
$this->className = $className;
}
public function problematicFunction($object){
if($object instanceof $this->className)
{
$object->test();
}
}
}
$object = new MyClass;
$another_object = new MyAnotherClass('MyClass');
$another_object->problematicFunction($object);
That's called type hinting and what you want to do is just not supported.
If all those dynamic class names have something in common (e.g., they're different implementations for certain feature) you probably want to define a base (maybe abstract) class or an interface and use that common ancestor as type hint:
<?php
interface iDatabase{
public function __contruct($url, $username, $password);
public function execute($sql, $params);
public function close();
}
class MyClass implements iDatabase{
public function __contruct($url, $username, $password){
}
public function execute($sql, $params){
}
public function close(){
}
}
class MyAnotherClass {
protected $className;
public function __construct($className){
$this->className = $className;
}
public function problematicFunction(iDatabase $object){
}
}
Otherwise, just move the check to within problematicFunction() body, as other answers explain.

Categories