I find a bug with php integrated to Ejabberd in authentication mode. This script, open php://stdin and php://stdout to read/write data, but after a while it will be hang so the authentication feature could not work.
My code:
<?php
/**
* A class extending EjabberdExternalAuth may optionally implement this interface
* to support additional, non-critical methods for adding and removing users.
*/
interface EjabberdExternalAuth_UserManagement {
/**
* Corresponds to `setpass` operation.
*/
public function setPassword($user, $server, $password);
/**
* Corresponds to `tryregister` operation.
*/
public function register($user, $server, $password);
/**
* Corresponds to `removeuser` operation.
*/
public function remove($user, $server);
/**
* Corresponds to `removeuser3` operation.
*/
public function removeSafely($user, $server, $password);
}
abstract class EjabberdExternalAuth {
private $db = null;
private $log = null;
private $stdin = null;
private $stdout = null;
public static $logLevel = array(LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_INFO, LOG_KERN);
/**
* Corresponds to `auth` operation.
*/
abstract protected function authenticate($user, $server, $password);
/**
* Corresponds to `isuser` operation.
*/
abstract protected function exists($user, $server);
final public function __construct(PDO $db = null, $log = null) {
set_error_handler(array($this, 'errorHandler'));
$this->db = $db;
$this->stdin = fopen('php://stdin', 'rb');
$this->stdout = fopen('php://stdout', 'wb');
if ($log) {
$this->log = fopen($log, 'a');
}
$this->log('Starting auth service...', LOG_INFO);
try {
$this->work();
} catch (Exception $e) {
$this->log($e->getMessage());
}
$this->log('Exiting...', LOG_INFO);
}
final protected function db() {
return $this->db;
}
final private function work() {
$this->log('Entering event loop...', LOG_INFO);
while (true) {
try {
$message = $this->read();
$message = $this->parseMessage($message);
} catch (UnexpectedValueException $e) {
$this->log($e->getMessage());
continue;
}
$this->log('Received message: ' . json_encode($message), LOG_DEBUG);
$response = false;
switch ($message['command']) {
case 'auth' :
$response = $this->authenticate($message['user'], $message['server'], $message['password']);
break;
case 'isuser' :
$response = $this->exists($message['user'], $message['server']);
break;
}
if ($this instanceof EjabberdExternalAuth_UserManagement) {
switch ($message['command']) {
case 'setpass' :
$response = $this->setPassword($message['user'], $message['server'], $message['password']);
break;
case 'tryregister' :
$response = $this->register($message['user'], $message['server'], $message['password']);
break;
case 'removeuser' :
$response = $this->remove($message['user'], $message['server']);
break;
case 'removeuser3' :
$response = $this->removeSafely($message['user'], $message['server'], $message['password']);
break;
}
}
$this->respond($response);
}
}
final private function read() {
$length = fgets($this->stdin, 3);
if (feof($this->stdin)) {
throw new RuntimeException('Pipe broken');
}
$length = current(unpack('n', $length));
if (!$length) {
throw new UnexpectedValueException("Invalid length value, won't continue reading");
}
$message = fgets($this->stdin, $length + 1);
return $message;
}
final private function parseMessage($message) {
$message = explode(':', $message, 4);
if (count($message) < 3) {
throw new UnexpectedValueException('Message is too short: ' . join(':', $message));
}
list($command, $user, $server) = $message;
$password = isset($message[3]) ? $message[3] : null;
return compact('command', 'user', 'server', 'password');
}
final private function respond($status) {
$message = pack('nn', 2, (int)$status);
$this->log('Sending response: ' . bin2hex($message), LOG_DEBUG);
fwrite($this->stdout, $message);
}
final protected function log($message, $severity = LOG_ERR) {
if ($this->log && in_array($severity, self::$logLevel, true)) {
static $types = array(
LOG_EMERG => 'EMERGENCY',
LOG_ALERT => 'ALERT',
LOG_CRIT => 'CRITICAL',
LOG_ERR => 'ERROR',
LOG_WARNING => 'WARNING',
LOG_NOTICE => 'NOTICE',
LOG_INFO => 'INFO',
LOG_DEBUG => 'DEBUG',
LOG_KERN => 'KERNEL'
);
$message = sprintf('%s <%s> %9s: %s', date('Y-m-d H:i:s'), getmypid(), isset($types[$severity]) ? $types[$severity] : $types[LOG_ERR], $message);
fwrite($this->log, $message . PHP_EOL);
}
}
final protected function errorHandler($errno, $errstr, $errfile, $errline, array $errcontext) {
$this->log("($errno) $errstr in $errfile on line $errline");
return false;
}
}
I think that, something is wrong, example: the STDOUT may be broken, so I need to write data to php://stdout forever, so how could I make sure the stream will never be broken.
Related
I've made a website in symfony. This website contains some forms with a checkbox choice and other fields.
The datas from the checkbox are serialized on flush.
It's all good.
Now i have to export this datas and i use the data exporter library from Sonata project. But the datas are still serialized and i have some things like that in my csv file:
a:2:{i:0;s:6:"Volets";i:1;s:22:"Panneau de remplissage";}
How can I unserialize my datas in order to have a clean csv file?
Here's my code
My controller
/**
* #Security("has_role('ROLE_WEBFORM')")
*/
public function exportAction(Request $request)
{
$filters = array();
$this->handleFilterForm($request, $filters);
if (!$filters['webform']) {
throw $this->createNotFoundException();
}
$webForm = $this->getRepository('CoreBundle:WebForm')->find($filters['webform']);
$source = new WebFormEntryIterator($webForm, $this->getEntityManager(), $this->get('ines_core.embedded_form.field_type_registry'), $filters);
return WebFormEntryExporter::createResponse('export.csv', $source);
}
and my class WebFormEntryExporter
class WebFormEntryExporter
{
public static function createResponse($filename, SourceIteratorInterface $source)
{
$writer = new CsvWriter('php://output', ';', '"', "", true, true);
$contentType = 'text/csv';
$callback = function() use ($source, $writer) {
$handler = \Exporter\Handler::create($source, $writer);
$handler->export();
};
return new StreamedResponse($callback, 200, [
'Content-Type' => $contentType,
'Content-Disposition' => sprintf('attachment; filename=%s', $filename)
]);
}
}
And my WebFormEntryIterator
class WebFormEntryIterator implements SourceIteratorInterface
{
protected $em;
protected $registry;
protected $repository;
protected $query;
protected $webForm;
protected $iterator;
public function __construct(WebForm $webForm, EntityManager $em, FieldTypeRegistry $registry, array $filters)
{
$this->webForm = $webForm;
$this->em = $em;
$this->registry = $registry;
$this->initQuery($filters);
}
/**
* {#inheritdoc}
*/
public function current()
{
$current = $this->iterator->current();
$entity = $current[0];
$data = [];
$data['ID'] = $entity->getId();
$data['Formulaire'] = $this->webForm->getName();
$data['Date de création'] = date_format($entity->getCreatedAt(), 'd/m/Y H:i:s');
foreach ($this->webForm->getEmbeddedFieldConfigs() as $fieldConfig) {
$header = $fieldConfig->getLabel();
$meta = $entity->getContentMeta($fieldConfig->getName());
$extension = $this->registry->get($meta->getFormat());
if (method_exists($extension, 'setEntityManager')) {
$extension->setEntityManager($this->em);
}
$value = $extension->formatMeta($meta);
$data[$header] = $value;
unset($extension);
}
$this->query->getEntityManager()->getUnitOfWork()->detach($current[0]);
unset($entity);
unset($webForm);
return $data;
}
/**
* {#inheritdoc}
*/
public function next()
{
$this->iterator->next();
}
/**
* {#inheritdoc}
*/
public function key()
{
return $this->iterator->key();
}
/**
* {#inheritdoc}
*/
public function valid()
{
return $this->iterator->valid();
}
/**
* {#inheritdoc}
*/
public function rewind()
{
if ($this->iterator) {
throw new InvalidMethodCallException('Cannot rewind a Doctrine\ORM\Query');
}
$this->iterator = $this->query->iterate();
$this->iterator->rewind();
}
protected function initQuery(array $filters)
{
$repository = $this->em->getRepository('InesCoreBundle:Content');
$qb = $repository->getWebFormEntryQueryBuilder();
$repository->applyWfeFilters($qb, $filters);
$this->query = $qb->getQuery();
}
}
Sorry for my broken English.
Thanks a lot
thanks chalasr.
I have to make the tratment in this file, in the current() function.
This is what I've done:
public function current()
{
$current = $this->iterator->current();
$entity = $current[0];
$data = [];
$data['ID'] = $entity->getId();
$data['Formulaire'] = $this->webForm->getName();
$data['Date de création'] = date_format($entity->getCreatedAt(), 'd/m/Y H:i:s');
foreach ($this->webForm->getEmbeddedFieldConfigs() as $fieldConfig) {
$header = $fieldConfig->getLabel();
$meta = $entity->getContentMeta($fieldConfig->getName());
$extension = $this->registry->get($meta->getFormat());
if (method_exists($extension, 'setEntityManager')) {
$extension->setEntityManager($this->em);
}
$value = $extension->formatMeta($meta);
if($this->is_serialized($value)) {
$value = unserialize($value);
$data[$header] = implode(' | ', $value);
} else {
$data[$header] = $value;
}
unset($extension);
}
$this->query->getEntityManager()->getUnitOfWork()->detach($current[0]);
unset($entity);
unset($webForm);
return $data;
}
public function is_serialized($data)
{
if (trim($data) == "") { return false; }
if (preg_match("/^(i|s|a|o|d){1}:{1}(.*)/si",$data)) { return true; }
return false;
}
I have this code:
class Service {
public function get_session($token) {
foreach ($this->config->sessions as $session) {
if ($token == $session->token) {
$session->last_access = date('r');
return $session;
}
}
return null;
}
public function mysql_connect($token, $host, $username, $password, $db) {
if (!$this->valid_token($token)) {
throw new Exception("Access Denied: Invalid Token");
}
// will throw exception if invalid
$this->mysql_create_connection($host, $username, $password, $db);
$session = $this->get_session($token);
$id = uniqid('res_');
if (!isset($session->mysql)) {
$session->mysql = new stdClass();
}
$mysql = &$session->mysql;
$mysql->$id = array(
'host' => $host,
'user' => $username,
'pass' => $password,
'name' => $db
);
return $id;
}
public function mysql_close($token, $res_id) {
if (!$this->valid_token($token)) {
throw new Exception("Access Denied: Invalid Token");
}
$session = $this->get_session($token);
if (!(isset($session->mysql->$res_id))) {
throw new Exception("Invalid resource id");
}
unset($session->mysql->$res_id);
if (empty((array)$session->mysql)) {
unset($session->mysql); // this don't work, don't know why
throw new Exception('isset($session->mysql) == ' .
(isset($session->mysql) ? 'true' : 'false'));
}
}
}
I call unset($session->mysql); if it's empty but the object is not removed, the exception throw true, How can I delete $session->mysql object? I've tried to add & in get_session but this didn't help.
Whole code can be found here.
You really should have posted your Session class in your post instead of linking to your GitHub repo... that's why the comments are confusing. You are using magic methods on your session class.
1 change I made: adding the magic __unset method.
Also, I had thought the constructor needed to be public but on further looking at it I was wrong about that (so my test code will not work unless the constructor is public... anyway...).
Here is the code below with the updated class:
<?
class Session {
public $storage;
public $token;
public $username;
public $browser;
public $start;
public $last_access;
private function __construct($u, $t, $s = null, $b = null, $d = null) {
$this->storage = $s ? $s : new stdClass();
$this->username = $u;
$this->token = $t;
$this->browser = $b ? $b : $_SERVER['HTTP_USER_AGENT'];
$this->start = $d ? $d : date('r');
}
function &__get($name) {
return $this->storage->$name;
}
function __set($name, $value) {
$this->storage->$name = $value;
}
function __isset($name) {
return isset($this->storage->$name);
}
function __unset($name) {
echo "Unsetting $name";
unset($this->storage->$name);
}
static function create_sessions($sessions) {
$result = array();
foreach ($sessions as $session) {
$result[] = new Session($session->username,
$session->token,
$session->storage,
$session->browser,
$session->start);
}
return $result;
}
static function cast($stdClass) {
$storage = $stdClass->storage ? $stdClass->storage : new stdClass();
return new Session($stdClass->username,
$stdClass->token,
$storage,
$stdClass->browser,
$stdClass->start);
}
static function new_session($username) {
return new Session($username, token());
}
}
And some test code:
$session = new Session('joe', '1234');
$session->mysql = 1234;
var_dump($session->mysql);
unset($session->mysql);
var_dump($session->mysql);
This is code of the added method:
function __unset($name) {
echo "Unsetting $name";
unset($this->storage->$name);
}
Check out the documentation to about the magic __unset method you need to add to your class:
http://php.net/manual/en/language.oop5.overloading.php#object.unset
__unset() is invoked when unset() is used on inaccessible properties.
i am writing a MySQL wrapper class to:
a.) replace a current one
b.) make life easier in terms of custom functionality
c.) to learn and improve
I am looking on advice on how to improve my techniques and coding style, so i would appreciate any input you could provide.
DISCLAIMER:
I am aware there are other database abstraction methods such as PDO but i wanted to write my own for the above reasons
Thanks,
Lee
Test Call
#!/usr/bin/php
<?php
include('mysql.class.php');
$_mysql = new mysqli_ls( array( 'debug_log' => TRUE, 'query_log' => TRUE ) );
if ($_mysql->connect('host','user','password','db') === FALSE) print_r( $_mysql->get_error() );
else print "#1 connected\n";
if ($_mysql->set_database('auth_tracker_test') === FALSE) print_r( $_mysql->get_error() );
else print "#1 database changed\n";
/// Execute standard query
$sql = "SELECT * from user";
if ($_mysql->SQLQuery($sql) === TRUE) print "#1 SELECT Query worked\n";
else print print_r( $_mysql->get_error() );
#print_r($_mysql->getArray());
print_r($_mysql->getRow());
$_mysql->disconnect();
?>
<?php
class mysqli_ls
{
/**************************************************************************/
/* SETUP VARIABLES */
/**************************************************************************/
private $E_OK = TRUE;
private $E_ERROR = FALSE;
private $db_host;
private $db_user;
private $db_port;
private $db_name;
private $db_pass;
private $result;
private $link = FALSE;
private $errorArr = array();
private $config = array( // Location of exisiting
'config_load' => FALSE,
'config_path' => 'database.cfg.php',
// Record errors to a file
'debug_log' => FALSE,
'debug_log_path' => '/tmp/mysql.debug.log',
// Record queries to a file
'query_log' => FALSE,
'query_log_path' => '/tmp/mysql.debug.log' );
private $fh_debug = FALSE;
private $fh_query = FALSE;
private $fh_config = FALSE;
/**************************************************************************/
/* MAGIC FUNCTIONS */
/**************************************************************************/
public function __construct( $config = '' )
{
// Config vars
if ( !empty($config) && is_array($config) ) $this->set_config($config);
// Open file handles if logs are required
// Debug Log
if ($this->config['debug_log'] === TRUE)
{
if (! $this->fh_debug = fopen($this->config['debug_log_path'], 'a') )
{
$this->handle_error('#01A', 'could not open debug log');
return $this->E_ERROR;
}
}
// Query Log
if ($this->config['query_log'] === TRUE)
{
if (! $this->fh_query = fopen($this->config['query_log_path'], 'a') )
{
$this->handle_error('#01B', 'could not open query log');
return $this->E_ERROR;
}
}
// Check mysqli functions are available
if (!function_exists('mysqli_connect'))
{
$this->handle_error('#01C', 'mysqli not installed');
return $this->E_ERROR;
}
return $this->E_OK;
}
public function __deconstruct()
{
if ($this->link) $this->disconnect();
return $this->E_OK;
}
/**************************************************************************/
/* CONNECTION MANAGEMENT */
/**************************************************************************/
public function connect($db_host='', $db_user='', $db_pass='', $db_name, $db_port='3306')
{
if (empty($db_host) || empty($db_user) || empty($db_pass) || empty($db_name) || empty($db_port))
{
$this->handle_error('#02A', 'Missing connection variables');
return $this->E_ERROR;
}
$this->db_host = $db_host;
$this->db_user = $db_user;
$this->db_pass = $db_pass;
$this->db_name = $db_name;
$this->db_port = $db_port;
$this->link = #new mysqli($this->db_host, $this->db_user, $this->db_pass, $this->db_name, $this->db_port);
if (mysqli_connect_error($this->link))
{
$this->handle_error(mysqli_connect_errno($this->link), mysqli_connect_error($this->link));
return $this->E_ERROR;
}
return $this->E_OK;
}
public function disconnect()
{
if ($this->link)
{
if ($this->link->close() === TRUE) return $this->E_OK;
else
{
$this->handle_error($this->link->errno, $this->link->error);
return $this->E_ERROR;
}
}
$this->handle_error('#03A','no activate database connection');
return $this->E_ERROR;
}
public function connect_existing()
{
}
public function set_database($database)
{
if ( $this->link->select_db($database) === FALSE )
{
$this->handle_error($this->link->errno, $this->link->error);
return $this->E_ERROR;
}
$this->E_OK;
}
/**************************************************************************/
/* SQL INTERFACE */
/**************************************************************************/
public function insert()
{
}
public function update()
{
}
public function delete()
{
}
public function select()
{
}
public function query($sql)
{
// If the result set has cleaned up, do so before a new query
if ($this->result) $this->result->close();
// Record query
if ($this->config['query_log'] === TRUE) $this->write_log('query', $sql);
if ($result = $this->link->query($sql));
{
$this->result = $result;
return $this->E_OK;
}
// Clean up the result set
$result->close();
// Query failed, handle error
$this->handle_error($this->link->errno, $this->link->error);
return $this->E_ERROR;
}
/**************************************************************************/
/* RESULT FUNCTIONS */
/**************************************************************************/
public function getArray($type = 'assoc')
{
switch($type)
{
case 'num':
$type = MYSQLI_NUM;
break;
case 'assoc':
$type = MYSQLI_ASSOC;
break;
case 'both':
$type = MYSQLI_BOTH;
break;
default:
$this->handle_error('#12A','invalid field type. Options are include num, assoc, both');
return $this->E_ERROR;
break;
}
$resultArr = array();
while( $row = $this->result->fetch_array( $type ) )
{
$resultArr[] = $row;
}
return $resultArr;
}
public function getRow($type = 'assoc')
{
switch($type)
{
case 'num':
$type = MYSQLI_NUM;
break;
case 'assoc':
$type = MYSQLI_ASSOC;
break;
case 'both':
$type = MYSQLI_BOTH;
break;
default:
$this->handle_error('#13A','invalid field type. Options are include num, assoc, both');
return $this->E_ERROR;
break;
}
return $this->result->fetch_array( $type );
}
public function num_row()
{
return $this->result->num_rows;
}
public function insert_id()
{
return $this->link->insert_id;
}
public function affected_rows()
{
return $this->link->affected_rows;
}
/**************************************************************************/
/* LEGACY SUPPORT */
/**************************************************************************/
public function SQLQuery($sql='')
{
if (empty($sql))
{
$this->handle_error('#19A','missing query string');
return $this->E_ERROR;
}
// Check for a select statement
if ( preg_match("/^select/i",$sql) === 0)
{
$this->handle_error('#19A','incorrect query type, SELECT expected');
return $this->E_ERROR;
}
// Execute query
if ($this->query($sql) === $this->E_ERROR) return $this->E_ERROR;
// Return number of rows
return $this->num_row();
}
public function SQLModify($sql='')
{
if (empty($sql))
{
$this->handle_error('#19A','missing query string');
return $this->E_ERROR;
}
// Execute query
if ($this->query($sql) === $this->E_ERROR) return $this->E_ERROR;
// Return affected rows
$this->affected_rows();
}
public function numRow()
{
return $this->num_row();
}
/**************************************************************************/
/* LOGGING AND DEBUGGING */
/**************************************************************************/
private function write_log($type, $msg)
{
$msg = date('Y-m-d H:i:s') ."\t". $type ."\t". $msg ."\n";
switch($type)
{
case 'error':
fwrite($this->fh_debug, $msg);
break;
case 'query':
fwrite($this->fh_query, $msg);
break;
default:
return $this->E_ERROR;
break;
}
}
private function handle_error($errormsg, $errorno)
{
$this->errorArr[] = array( 'code' => $errorno,
'error' => $errormsg );
if ($this->config['debug_log'] === TRUE)
{
$msg = "($errorno) $errormsg";
$this->write_log('error', $msg);
}
return $this->E_OK;
}
public function get_error($type = 'string')
{
switch($string)
{
case 'string':
$error = end($this->errorArr);
return $error['error'] .' ('. $error['code'] .')';
break;
case 'array':
return end($this->errorArr);
break;
}
return false;
}
/**************************************************************************/
/* SET CONFIG VARS */
/**************************************************************************/
public function set_config($config)
{
foreach ($config as $key => &$value)
{
if ( ! isset($this->config[$key]) )
{
$this->handle_error('#19A','invalid field type');
return $this->E_ERROR;
}
$this->config[$key] = $value;
}
return $this->E_OK;
}
/**************************************************************************/
} // Class END
?>
I made one myself once and added another class named MySqlTable, which represented a table. I returned it in the __GET function, so you could call a table with
$sql->tablename->select();
Here's the code for the __get function:
function __GET($name)
{
return new MySqlTable($this, $name);
}
The class was like this:
class MySqlTable
{
private $table;
private $mySql;
function MySqlTable(&$oMySql, $sTable)
{
$this->mySql = $oMySql;
$this->table = $sTable;
}
function &select($sWhere = '')
{
if (empty($sWhere))
{
$data = $this->mySql->query("SELECT * FROM " . $this->table);
}
else
{
$data = $this->mySql->query("SELECT * FROM " . $this->table . " WHERE " . $sWhere);
}
return $this->mySql->resultToArray($data);
}
}
Currently, your mysqli_ls class contains the result of a query. This makes it impossible to do two queries and use the results of the first query after the second query ran.
A better way would be to let the SQLQuery() method return a result object, which contains the result handle and methods to retrieve rows from the result.
So I want to find the best method of allowing my Logger class to access any part of the script either another class/function/etc... How would I do this? How do I make it global.
Could I do something like this:
Logger::info('Add message like this?');
Calling script: calling.php
require_once('Logger.class.php'); // Just adding the class initializes the Logger Object
require_once('Another.class.php');
require_once('Functions.php');
$logEntry->info("Log info");
$logEntry->error("Log error");
$logEntry->warning("Log warning");
$logEntry->notice("Log notice");
$logEntry->enableDebug(); // prints debug to log
$logEntry->debug("Log debug enabled");
$logEntry->disableDebug();
$logEntry->debug("Log debug disabled"); // will not print to log
$another_obj = new Another(); // want the Logger to have access inside this class
More(); // want the Logger to have access inside this function
Another.class.php
class Another {
private $var;
// I want to add the Logger here
$logEntry->info("Another Log");
// More code here it's just an example ...
}
Functions.php
function More() {
// I want to add the Logger here
$logEntry->info("More Log");
}
Here is the Logger.class.php script
<?php
//Define Constants
define("LOG_FILE_DIRECTORY", "/var/www/logs");
ini_set("memory_limit","128M"); // Logger class is taking up memory
class Logger {
private $log_file_directory = LOG_FILE_DIRECTORY;
private $first_run; // Flag to add line break at the beginning of script execution
private $calling_script; // Base name of the calling script
private $log_file; // log file path and name
private $log_entry; // information to be logged
private $log_level; // Log severity levels: error, warning, notice, debug, info
private $fh; // File handle
private $file_name; // File path and name
private $file_parts; // Array of $file_name
private $script_name; // Script Name
private $script_parts; // Array of $script_name
private $line_number_arr; // Line number of where the logging event occurred
private $debug_flag; // Set to true if you want to log your debug logger
function __construct() {
$this->first_run = true;
$this->debug_flag = false;
$this->calling_script = '';
$this->log_file = '';
$this->log_entry = '';
$this->log_level = '';
$this->fh = '';
$this->file_name = '';
$this->file_parts = '';
$this->script_name = '';
$this->script_parts = '';
$this->line_number_arr = '';
}
/**
* #enableDebug
*/
public function enableDebug() {
$this->debug_flag = true;
}
/**
* #disbaleDebug
*/
public function disableDebug() {
$this->debug_flag = false;
}
/**
* #info
*/
public function info($message) {
$this->log_level = 'info';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #error
*/
public function error($message) {
$this->log_level = 'error';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #warning
*/
public function warning($message) {
$this->log_level = 'warning';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #notice
*/
public function notice($message) {
$this->log_level = 'notice';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #debug
* must add the below to the script you wish to debug
* define("DEBUG", true); // true enables, false disables
*/
public function debug($message) {
if($this->debug_flag) {
$this->log_level = 'debug';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
}
private function addEntry($message) {
$this->calling_script = $this->getScriptBaseName();
$this->log_file = $this->log_file_directory."/".$this->calling_script.".log";
$this->fh = fopen($this->log_file, 'a') or die("Can't open log file: ".$this->log_file);
if($this->first_run) {
$this->log_entry = "\n[" . date("Y-m-d H:i:s", mktime()) . "][line:".$this->line_number_arr[0]['line']."|".$this->log_level."]:\t".$message."\n";
} else {
$this->log_entry = "[" . date("Y-m-d H:i:s", mktime()) . "][line:".$this->line_number_arr[0]['line']."|".$this->log_level."]:\t".$message."\n";
}
fwrite($this->fh, $this->log_entry);
fclose($this->fh);
$this->first_run = false;
}
/**
* return the base name of the calling script
*/
private function getScriptBaseName() {
$this->file_name = $_SERVER["SCRIPT_NAME"];
$this->file_parts = explode('/', $this->file_name);
$this->script_name = $this->file_parts[count($this->file_parts) - 1];
$this->script_parts = explode('.', $this->script_name);
// If file doesn't exists don't add line break
if(!file_exists($this->script_parts[0].".log")) {
$this->first_run = false;
}
return $this->script_parts[0];
}
}
// Start log instance
$logEntry = new Logger();
?>
You could implement it as a class full of static functions, e.g.:
class Logger {
protected $logfile = null;
public static load() {
self::$logfile = fopen('error.log', 'a');
}
public static info($msg) {
if(self::$logfile == null)
self::load();
fwrite(self::$logfile, $msg);
}
}
and then use it with Logger::info("My message..");. Another common method of doing this is using a singleton class, so that you can only create a single object of "Logger" and retrieve it using e.g. Logger::getInstance()->logInfo("My message");.
In your case (as you already implemented Logger as a normal class) I would make the __construct private and implement the class as a singleton. It's not possible to make $logEntry globally available. Your code would become:
<?php
//Define Constants
define("LOG_FILE_DIRECTORY", "/var/www/logs");
ini_set("memory_limit","128M"); // Logger class is taking up memory
class Logger {
private $log_file_directory = LOG_FILE_DIRECTORY;
private $first_run; // Flag to add line break at the beginning of script execution
private $calling_script; // Base name of the calling script
private $log_file; // log file path and name
private $log_entry; // information to be logged
private $log_level; // Log severity levels: error, warning, notice, debug, info
private $fh; // File handle
private $file_name; // File path and name
private $file_parts; // Array of $file_name
private $script_name; // Script Name
private $script_parts; // Array of $script_name
private $line_number_arr; // Line number of where the logging event occurred
private $debug_flag; // Set to true if you want to log your debug logger
private static $instance = null;
private function __construct() {
$this->first_run = true;
$this->debug_flag = false;
$this->calling_script = '';
$this->log_file = '';
$this->log_entry = '';
$this->log_level = '';
$this->fh = '';
$this->file_name = '';
$this->file_parts = '';
$this->script_name = '';
$this->script_parts = '';
$this->line_number_arr = '';
}
public static function getInstance() {
if (!isset(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c;
}
return self::$instance;
}
/**
* #enableDebug
*/
public function enableDebug() {
$this->debug_flag = true;
}
/**
* #disbaleDebug
*/
public function disableDebug() {
$this->debug_flag = false;
}
/**
* #info
*/
public function info($message) {
$this->log_level = 'info';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #error
*/
public function error($message) {
$this->log_level = 'error';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #warning
*/
public function warning($message) {
$this->log_level = 'warning';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #notice
*/
public function notice($message) {
$this->log_level = 'notice';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #debug
* must add the below to the script you wish to debug
* define("DEBUG", true); // true enables, false disables
*/
public function debug($message) {
if($this->debug_flag) {
$this->log_level = 'debug';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
}
private function addEntry($message) {
$this->calling_script = $this->getScriptBaseName();
$this->log_file = $this->log_file_directory."/".$this->calling_script.".log";
$this->fh = fopen($this->log_file, 'a') or die("Can't open log file: ".$this->log_file);
if($this->first_run) {
$this->log_entry = "\n[" . date("Y-m-d H:i:s", mktime()) . "][line:".$this->line_number_arr[0]['line']."|".$this->log_level."]:\t".$message."\n";
} else {
$this->log_entry = "[" . date("Y-m-d H:i:s", mktime()) . "][line:".$this->line_number_arr[0]['line']."|".$this->log_level."]:\t".$message."\n";
}
fwrite($this->fh, $this->log_entry);
fclose($this->fh);
$this->first_run = false;
}
/**
* return the base name of the calling script
*/
private function getScriptBaseName() {
$this->file_name = $_SERVER["SCRIPT_NAME"];
$this->file_parts = explode('/', $this->file_name);
$this->script_name = $this->file_parts[count($this->file_parts) - 1];
$this->script_parts = explode('.', $this->script_name);
// If file doesn't exists don't add line break
if(!file_exists($this->script_parts[0].".log")) {
$this->first_run = false;
}
return $this->script_parts[0];
}
}
?>
You would then use your class globally using Logger::getInstance()->info($msg);
I keep getting the following exception with a new resource Im making and i cant figure out why:
PHP Fatal error: Uncaught exception 'Zend_Application_Bootstrap_Exception' with message 'Circular resource dependency detected' in /opt/local/lib/php/Zend/Application/Bootstrap/BootstrapAbstract.php:656
Stack trace:
#0 /opt/local/lib/php/Zend/Application/Bootstrap/BootstrapAbstract.php(623): Zend_Application_Bootstrap_BootstrapAbstract->_executeResource('modules')
#1 /opt/local/lib/php/Zend/Application/Bootstrap/BootstrapAbstract.php(580): Zend_Application_Bootstrap_BootstrapAbstract->_bootstrap('modules')
#2 /Library/WebServer/Documents/doctrine-dev/library/APP/Doctrine/Application/Resource/Doctrine.php(36): Zend_Application_Bootstrap_BootstrapAbstract->bootstrap('modules')
#3 /opt/local/lib/php/Zend/Application/Bootstrap/BootstrapAbstract.php(708): APP_Doctrine_Application_Resource_Doctrine->__construct(Array)
#4 /opt/local/lib/php/Zend/Application/Bootstrap/BootstrapAbstract.php(349): Zend_Application_Bootstrap_BootstrapAbstract->_loadPluginResource('doctrine', Array)
#5 /opt/local/lib/php/Zend/Application/Bootstrap/Bootstra in /opt/local/lib/php/Zend/Application/Bootstrap/BootstrapAbstract.php on line 656
As you'll see below ive created a Doctrine Resource that should load only in the general application bootstrap. In order to perform its tasks it needs the Modules resource to be bootstraped
so it calls $this->getBootstrap()->bootstrap('modules'). the Modules resoure never calls Doctrine though, and this seems to be where i get the circular dependency. Ive tried the code that is currently in the constructor for my Doctrine resource also as part of init directly and that doesnt seem to work either. An even bigger mystery to me though is that if i call $bootstrap->bootstrap('modules')
by itself before calling $bootstrap->bootstrap('doctrine') it all seems to play nicely. So why is there circular reference issue?
Relevant parts of application.xml
<resources>
<frontController>
<controllerDirectory><zf:const zf:name="APPLICATION_PATH" />/controllers</controllerDirectory>
<moduleDirectory><zf:const zf:name="APPLICATION_PATH" />/modules</moduleDirectory>
<moduleControllerDirectoryName value="controllers" />
</frontController>
<modules prefixModuleName="Mod" configFilename="module.xml">
<enabledModules>
<default />
<doctrinetest />
<cms>
<myOption value="Test Option Value" />
</cms>
<menu somevar="menu" />
<article somevar="article" />
</enabledModules>
</modules>
<doctrine>
<connections>
<default dsn="mysql://#####:######localhost/#####">
<attributes useNativeEnum="1" />
</default>
</connections>
<attributes>
<autoAccessorOverride value="1" />
<autoloadTableClasses value="1" />
<modelLoading value="MODEL_LOADING_PEAR" />
</attributes>
<directoryNames>
<sql value="data/sql" />
<fixtures value="data/fixtures" />
<migrations value="data/migrations" />
<yaml value="configs/schemas" />
<models value="models" />
</directoryNames>
</doctrine>
</resources>
Doctrine Resource
<?php
class APP_Doctrine_Application_Resource_Doctrine extends Zend_Application_Resource_ResourceAbstract
{
protected $_manager = null;
protected $_modules = array();
protected $_attributes = null;
protected $_connections = array();
protected $_defaultConnection = null;
protected $_directoryNames = null;
protected $_inflectors = array();
public function __construct($options = null)
{
parent::__construct($options);
$bootstrap = $this->getBootstrap();
$autoloader = $bootstrap->getApplication()->getAutoloader();
$autoloader->pushAutoloader(array('Doctrine_Core', 'autoload'), 'Doctrine');
spl_autoload_register(array('Doctrine_Core', 'modelsAutoload'));
$manager = $this->getManager();
$manager->setAttribute('bootstrap', $bootstrap);
// default module uses the application bootstrap unless overridden!
$modules = array('default' => $bootstrap);
if(!isset($options['useModules']) ||
(isset($options['useModules']) && (boolean) $options['useModules']))
{
$moduleBootstraps = $bootstrap->bootstrap('modules')->getResource('modules');
$modules = array_merge($modules, $moduleBootstraps->getArrayCopy());
}
$this->setModules($modules); // configure the modules
$this->_loadModels(); // load all the models for Doctrine
}
public function init()
{
return $this->getManager();
}
public function setConnections(array $connections)
{
$manager = $this->getManager();
foreach($connections as $name => $config)
{
if(isset($config['dsn']))
{
$conn = $manager->connection($config['dsn'], $name);
}
if(isset($config['attributes']) && isset($conn))
{
$this->setAttributes($config['attributes'], $conn);
}
}
return $this;
}
public function setAttributes(array $attributes, Doctrine_Configurable $object = null)
{
if($object === null)
{
$object = $this->getManager();
}
foreach($attributes as $name => $value)
{
$object->setAttribute(
$this->doctrineConstant($name, 'attr'),
$this->doctrineConstant($value)
);
}
return $this;
}
public function setModules(array $modules)
{
//$this->_modules = $modules;
foreach($modules as $name => $bootstrap)
{
$this->_modules[$name] = $this->_configureModuleOptions($bootstrap);
}
return $this;
}
public function setDirectoryNames(array $directoryNames)
{
$this->_directoryNames = $directoryNames;
return $this;
}
public function getDirectoryNames()
{
return $this->_directoryNames;
}
public function getDirectoryName($key)
{
if(isset($this->_directoryNames[$key]))
{
return $this->_directoryNames[$key];
}
return null;
}
public function getModuleOptions($module = null)
{
if($module === null)
{
return $this->_modules;
}
if(isset($this->_modules[$module]))
{
return $this->_modules[$module];
}
return null;
}
public function doctrineConstant($value, $prefix = '')
{
if($prefix !== '')
{
$prefix .= '_';
}
$const = $this->_getConstantInflector()->filter(array(
'prefix'=>$prefix,
'key' => $value
));
$const = constant($const);
return $const !== null ? $const : $value;
}
/**
* getManager
* #return Doctrine_Manager
*/
public function getManager()
{
if(!$this->_manager)
{
$this->_manager = Doctrine_Manager::getInstance();
}
return $this->_manager;
}
protected function _getConstantInflector()
{
if(!isset($this->_inflectors['constant']))
{
$callback = new Zend_Filter_Callback(array('callback'=>'ucfirst'));
$this->_inflectors['constant'] = new Zend_Filter_Inflector(
'Doctrine_Core::#prefix#key',
array(
':prefix' => array($callback, 'Word_CamelCaseToUnderscore', 'StringToUpper'),
':key' => array('Word_SeparatorToCamelCase', 'Word_CamelCaseToUnderscore', 'StringToUpper')
), null, '#');
}
return $this->_inflectors['constant'];
}
protected function _configureModuleOptions(Zend_Application_Bootstrap_BootstrapAbstract $bootstrap)
{
$coreBootstrapClass = get_class($this->getBootstrap());
if(get_class($bootstrap) === $coreBootstrapClass)
{
// handled differently
$resourceLoader = $bootstrap->bootstrap('DefaultAutoloader')->getResource('DefaultAutoloader');
$moduleName = $resourceLoader->getNamespace();
}
else
{
// handle a module bootstrap
$resourceLoader = $bootstrap->getResourceLoader();
$moduleName = $bootstrap->getModuleName();
}
$resourceTypes = $resourceLoader->getResourceTypes();
$modelResource = isset($resourceTypes['model'])
? $resourceTypes['model']
: array('path'=>'models', 'namespace'=>'Model');
$modulePath = $resourceLoader->getBasePath();
$classPrefix = $modelResource['namespace'];
$modelsPath = $modelResource['path'];
$doctrineOptions = array(
'generateBaseClasses'=>TRUE,
'generateTableClasses'=>TRUE,
'baseClassPrefix'=>'Base_',
'baseClassesDirectory'=> NULL,
'baseTableClassName'=>'Doctrine_Table',
'generateAccessors' => true,
'classPrefix'=>"{$classPrefix}_",
'classPrefixFiles'=>FALSE,
'pearStyle'=>TRUE,
'suffix'=>'.php',
'phpDocPackage'=> $moduleName,
'phpDocSubpackage'=>'Models',
);
$doctrineConfig = array(
'data_fixtures_path' => "$modulePath/{$this->getDirectoryName('fixtures')}",
'models_path' => "$modelsPath",
'migrations_path' => "$modulePath/{$this->getDirectoryName('migrations')}",
'yaml_schema_path' => "$modulePath/{$this->getDirectoryName('yaml')}",
'sql_path' => "$modulePath/{$this->getDirectoryName('sql')}",
'generate_models_options' => $doctrineOptions
);
return $doctrineConfig;
}
protected function _loadModels()
{
$moduleOptions = $this->getModuleOptions();
foreach($moduleOptions as $module => $options)
{
Doctrine_Core::loadModels(
$options['models_path'],
Doctrine_Core::MODEL_LOADING_PEAR,
$options['generate_models_options']['classPrefix']
);
}
return $this;
}
}
Modules Resource
<?php
class APP_Application_Resource_Modules extends Zend_Application_Resource_Modules
{
protected $_prefixModuleNames = false;
protected $_moduleNamePrefix = null;
protected $_defaultModulePrefix = 'Mod';
protected $_configFileName = 'module.xml';
protected $_enabledModules = null;
public function __construct($options = null)
{
if(isset($options['prefixModuleName']))
{
if(($prefix = APP_Toolkit::literalize($options['prefixModuleName']))
!== false)
{
$this->_prefixModuleNames = true;
$this->_moduleNamePrefix = is_string($prefix)
? $prefix
: $this->_defaultModulePrefix;
}
}
if(isset($options['configFileName']))
{
$this->_configFileName = $options['configFileName'];
}
parent::__construct($options);
}
protected function _mergeModuleConfigs(array $applicationConfig)
{
$cacheManager = $this->_getCacheManager();
if(isset($applicationConfig['resources']['modules']['enabledModules']))
{
$applicationModulesOptions = &$applicationConfig['resources']['modules'];
$enabledModules = &$applicationModulesOptions['enabledModules'];
$front = $this->getBootstrap()->getResource('frontcontroller');
foreach($enabledModules as $moduleName => $moduleOptions)
{
// cache testing
// note cache keys for modules are prefixed if prefix is enabled #see _formatModuleName
if(!$cacheManager->test('config', $this->_formatModuleName($moduleName)))
{
$configPath = $front->getModuleDirectory($moduleName).'/configs/'.$this->getConfigFilename();
if(file_exists($configPath))
{
if(strpos($configPath, ".xml") === false)
{
throw new Exception(__CLASS__." is only compatible with XML configuration files.");
}
$config = new Zend_Config_Xml($configPath);
$enabledModules[$moduleName] = array_merge((array) $moduleOptions, $config->toArray());
//$this->setOptions($options);
$cacheManager->save('config', $enabledModules[$moduleName], $this->_formatModuleName($moduleName));
}
}
else
{
$options = $cacheManager->load('config', $this->_formatModuleName($moduleName));
$enabledModules[$moduleName] = array_merge((array) $enabledModules[$moduleName], $options);
}
}
}
return $applicationConfig;
}
public function init()
{
/**
* #var Zend_Application_Bootstrap_BoostrapAbstract
*/
$bootstrap = $this->getBootstrap();
if(!$bootstrap->hasResource('frontController'))
{
$bootstrap->bootstrap('frontController');
}
$front = $bootstrap->getResource('frontController');
$applicationConfig = $this->_mergeModuleConfigs($bootstrap->getOptions());
$bootstrap->setOptions($applicationConfig);
parent::init();
return $this->_bootstraps;
}
/**
* Format a module name to the module class prefix
*
* #param string $name
* #return string
*/
protected function _formatModuleName($name)
{
$name = strtolower($name);
$name = str_replace(array('-', '.'), ' ', $name);
$name = ucwords($name);
$name = str_replace(' ', '', $name);
$options = $this->getOptions();
if($this->prefixEnabled())
{
$name = $this->getModuleNamePrefix().$name;
}
return $name;
}
protected function _getCacheManager()
{
$bootstrap = $this->getBootstrap();
if(!$bootstrap->hasResource('cacheManager'))
{
$bootstrap->bootstrap('cacheManager');
}
return $bootstrap->getResource('cacheManager');
}
public function prefixEnabled()
{
return $this->_prefixModuleNames;
}
public function getModuleNamePrefix()
{
return $this->_moduleNamePrefix;
}
public function getConfigFilename()
{
return $this->_configFileName;
}
public function setEnabledModules($modules)
{
$this->_enabledModules = (array) $modules;
}
public function getEnabledModules($controllerDirectories = null)
{
if($controllerDirectories instanceof Zend_Controller_Front)
{
$controllerDirectories = $controllerDirectories->getControllerDirectory();
}
if(is_array($controllerDirectories))
{
$options = $this->getOptions();
$enabledModules = isset($options['enabledModules'])
? (array) $options['enabledModules']
: array();
$this->_enabledModules = array_intersect_key($controllerDirectories, $enabledModules);
}
elseif(null !== $controllerDirectories)
{
throw new InvalidArgumentException('Argument must be an instance of
Zend_Controller_Front or an array mathing the format of the
return value of Zend_Controller_Front::getControllerDirectory().'
);
}
return $this->_enabledModules;
}
public function setPrefixModuleName($value)
{
$this->_prefixModuleNames = APP_Toolkit::literalize($value);
}
}
Module Bootstrap Base Class
<?php
class APP_Application_Module_Bootstrap extends Zend_Application_Module_Bootstrap
{
public function _initResourceLoader()
{
$loader = $this->getResourceLoader();
$loader->addResourceType('actionhelper', 'helpers', 'Action_Helper');
}
}
Maybe this section of the e-book "Survive the Deep End" might help you : 6.6. Step 5: Fixing ZFExt_Bootstrap -- it specifically speaks about the error you are getting.
(I won't quote as there is quite a couple of long paragraphs, but, hopefully, this'll help)