Can't delete object property in php - php

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.

Related

PHP constructor in child class overwrites the one in parent. So parent constructor does not run. OpenCart

I am making a web export from our program to OpenCart. I am trying to log in, but i get this error message:
PHP Fatal error: Call to a member function get() on null in /home/key2demo/domains/key2datafeed.com/public_html/ocdemoshops/oc23/system/engine/controller.php on line 10
We find out that the constructor in child class overwrites the one in parent. So parent constructor does not run and does not set this->registry.
The code in the controller is:
<?php
abstract class Controller {
protected $registry;
public function __construct($registry) {
$this->registry = $registry;
}
public function __get($key) {
return $this->registry->get($key);
}
public function __set($key, $value) {
$this->registry->set($key, $value);
}
}
This is The code i made:
define("VERSION", "1.0");
define("LANGUAGE", "1");
if (is_file('./../admin/config.php')) {
require_once('./../admin/config.php');
}
require_once(DIR_SYSTEM . 'startup.php');
$application_config = 'admin';
$registry = new Registry();
$loader = new Loader($registry);
$registry->set('load', $loader);
$config = new Config();
$config->load('default');
$config->load($application_config);
$registry->set('config', $config);
$registry->set('request', new Request());
$response = new Response();
$response->addHeader('Content-Type: text/html; charset=utf-8');
$registry->set('response', $response);
$registry->set('cache', new Cache($config->get('cache_type'), $config-
>get('cache_expire')));
$registry->set('url', new Url($config->get('site_ssl')));
$language = new Language($config->get('language_default'));
$language->load($config->get('language_default'));
$registry->set('language', $language);
$registry->set('document', new Document());
$event = new Event($registry);
$registry->set('event', $event);
if ($config->get('db_autostart')) {
$registry->set('db', new DB($config->get('db_type'), $config-
>get('db_hostname'), $config->get('db_username'), $config-
>get('db_password'), $config->get('db_database'), $config-
>get('db_port')));
}
if ($config->get('session_autostart')) {
$session = new Session();
$session->start();
$registry->set('session', $session);
}
if ($config->has('action_event')) {
foreach ($config->get('action_event') as $key => $value) {
$event->register($key, new Action($value));
}
}
if ($config->has('config_autoload')) {
foreach ($config->get('config_autoload') as $value) {
$loader->config($value);
}
}
if ($config->has('language_autoload')) {
foreach ($config->get('language_autoload') as $value) {
$loader->language($value);
}
}
if ($config->has('library_autoload')) {
foreach ($config->get('library_autoload') as $value) {
$loader->library($value);
}
}
if ($config->has('model_autoload')) {
foreach ($config->get('model_autoload') as $value) {
$loader->model($value);
}
}
class K2P_API_OCWRITER extends Controller
{
private $errors;
private $admin;
private $adminValidated;
private $adminShops;
public function __construct()
{
$this->errors = array();
}
public function doLog($message)
{
file_put_contents('./key2_log.txt', $message, FILE_APPEND);
}
public function login($usr, $pwd)
{
if ($this->user->login($usr, $pwd)) {
return true;
$this->doLog('logged in');
} else {
$this->doLog('Failed to login, please supply a valid
username/password and check your webshop url');
die;
}
}
public function getLanguages()
{
}
}
$db = new DB(DB_DRIVER, DB_HOSTNAME, DB_USERNAME, DB_PASSWORD, DB_DATABASE);
$registry->set('db', $db);
$registry->set('user', new Cart\User($registry));
$registry->set('tax', new Cart\Tax($registry));
$myAPI = new K2P_API_OCWRITER($registry);
$myAPI->config->set("config_language_id",LANGUAGE);
$command = $myAPI->cleanPost($_POST['command']);
$steps = $myAPI->cleanPost($_POST['steps']);
$page = $myAPI->cleanPost($_POST['page']);
$usr = $myAPI->cleanPost($_POST['usr']);
$pwd = $myAPI->cleanPost($_POST['pwd']);
//$myAPI->doLog(PHP_EOL . 'pages: ' . $page);
//$myAPI->doLog(PHP_EOL . 'steps: ' . $steps);
$totalProducts = $myAPI->getProductCount();
if ($myAPI->checkInput($usr,$pwd,$command,$page,$steps)) {
if ($myAPI->login($usr, $pwd)) {
switch($command){
case "getCategoryCount":
echo json_encode($myAPI->getCategoryCount(),JSON_FORCE_OBJECT
| JSON_UNESCAPED_SLASHES);
break;
case "getProductCount";
echo json_encode($myAPI->getProductCount(),JSON_FORCE_OBJECT |
JSON_UNESCAPED_SLASHES);
break;
case "getCategories":
echo json_encode($myAPI->getCategories($steps, $page,
JSON_FORCE_OBJECT | JSON_UNESCAPED_SLASHES));
break;
case "getProducts":
echo json_encode($myAPI->getProducts($steps, $page,
JSON_FORCE_OBJECT | JSON_UNESCAPED_SLASHES));
break;
default:
echo "Invalid command!";
break;
}
}
}
If i add parent::__construct(); to it. It still don't work. I didn't know by which one I had to add it, so i tried both.
When i added parent::__construct(); to the controller like this:
<?php
abstract class Controller {
protected $registry;
public function __construct($registry) {
parent::__construct();
$this->registry = $registry;
}
public function __get($key) {
return $this->registry->get($key);
}
public function __set($key, $value) {
$this->registry->set($key, $value);
}
}
Then i get this error message:
PHP Fatal error: Call to a member function get() on null in /home/key2demo/domains/key2datafeed.com/public_html/ocdemoshops/oc23/system/engine/controller.php on line 11
And if i add it to my made code like this:
public function __construct()
{
parent::__construct();
$this->errors = array();
}
Then i get this error messages:
PHP Warning: Missing argument 1 for Controller::__construct(), called in /home/key2demo/domains/key2datafeed.com/public_html/ocdemoshops/oc23/key2publish/k2p_api_OCwriter.php on line 95 and defined in /home/key2demo/domains/key2datafeed.com/public_html/ocdemoshops/oc23/system/engine/controller.php on line 5
PHP Notice: Undefined variable: registry in /home/key2demo/domains/key2datafeed.com/public_html/ocdemoshops/oc23/system/engine/controller.php on line 6
PHP Fatal error: Call to a member function get() on null in /home/key2demo/domains/key2datafeed.com/public_html/ocdemoshops/oc23/system/engine/controller.php on line 10
Does anyone know how to fix this? I would like to hear.
Thanks!
Constructor of your Controller class takes $registry as argument. So when you call __construct of Controller class, you need to call it like:
parent::__construct($registry);
So, your constructor for K2P_API_OCWRITER which Controller can be:
class K2P_API_OCWRITER extends Controller
{
public function __construct($registry)
{
// pass `$registry` to parent `__construct`
parent::__construct($registry);
$this->errors = array();
}
}
And instantiating an object of K2P_API_OCWRITER is still:
$myAPI = new K2P_API_OCWRITER($registry);
And btw there's no need to write parent::__construct(); in Controller constructor, as it does not extend any classes, so it does not have parent.

ERROR: call to a member function get() on null

I got this error message:
PHP Fatal error: Call to a member function get() on null in /home/key2demo/domains/key2datafeed.com/public_html/ocdemoshops/oc23/system/engine/controller.php on line 10
All the code in the controller is:
<?php
abstract class Controller {
protected $registry;
public function __construct($registry) {
$this->registry = $registry;
}
public function __get($key) {
return $this->registry->get($key);
}
public function __set($key, $value) {
$this->registry->set($key, $value);
}
}
The code i use registry in is this:
define("VERSION", "1.0");
define("LANGUAGE", "1");
if (is_file('./../admin/config.php')) {
require_once('./../admin/config.php');
}
require_once(DIR_SYSTEM . 'startup.php');
$application_config = 'admin';
$registry = new Registry();
$loader = new Loader($registry);
$registry->set('load', $loader);
$config = new Config();
$config->load('default');
$config->load($application_config);
$registry->set('config', $config);
$registry->set('request', new Request());
$response = new Response();
$response->addHeader('Content-Type: text/html; charset=utf-8');
$registry->set('response', $response);
$registry->set('cache', new Cache($config->get('cache_type'), $config-
>get('cache_expire')));
$registry->set('url', new Url($config->get('site_ssl')));
$language = new Language($config->get('language_default'));
$language->load($config->get('language_default'));
$registry->set('language', $language);
$registry->set('document', new Document());
$event = new Event($registry);
$registry->set('event', $event);
if ($config->get('db_autostart')) {
$registry->set('db', new DB($config->get('db_type'), $config-
>get('db_hostname'), $config->get('db_username'), $config-
>get('db_password'), $config->get('db_database'), $config-
>get('db_port')));
}
if ($config->get('session_autostart')) {
$session = new Session();
$session->start();
$registry->set('session', $session);
}
if ($config->has('action_event')) {
foreach ($config->get('action_event') as $key => $value) {
$event->register($key, new Action($value));
}
}
if ($config->has('config_autoload')) {
foreach ($config->get('config_autoload') as $value) {
$loader->config($value);
}
}
if ($config->has('language_autoload')) {
foreach ($config->get('language_autoload') as $value) {
$loader->language($value);
}
}
if ($config->has('library_autoload')) {
foreach ($config->get('library_autoload') as $value) {
$loader->library($value);
}
}
if ($config->has('model_autoload')) {
foreach ($config->get('model_autoload') as $value) {
$loader->model($value);
}
}
class K2P_API_OCWRITER extends Controller
{
private $errors;
private $admin;
private $adminValidated;
private $adminShops;
public function __construct()
{
$this->errors = array();
}
public function doLog($message)
{
file_put_contents('./key2_log.txt', $message, FILE_APPEND);
}
public function login($usr, $pwd)
{
if ($this->user->login($usr, $pwd)) {
return true;
$this->doLog('logged in');
} else {
$this->doLog('Failed to login, please supply a valid
username/password and check your webshop url');
die;
}
}
public function getLanguages()
{
}
}
$db = new DB(DB_DRIVER, DB_HOSTNAME, DB_USERNAME, DB_PASSWORD, DB_DATABASE);
$registry->set('db', $db);
$registry->set('user', new Cart\User($registry));
$registry->set('tax', new Cart\Tax($registry));
$myAPI = new K2P_API_OCWRITER($registry);
$myAPI->config->set("config_language_id",LANGUAGE);
$command = $myAPI->cleanPost($_POST['command']);
$steps = $myAPI->cleanPost($_POST['steps']);
$page = $myAPI->cleanPost($_POST['page']);
$usr = $myAPI->cleanPost($_POST['usr']);
$pwd = $myAPI->cleanPost($_POST['pwd']);
//$myAPI->doLog(PHP_EOL . 'pages: ' . $page);
//$myAPI->doLog(PHP_EOL . 'steps: ' . $steps);
$totalProducts = $myAPI->getProductCount();
if ($myAPI->checkInput($usr,$pwd,$command,$page,$steps)) {
if ($myAPI->login($usr, $pwd)) {
switch($command){
case "getCategoryCount":
echo json_encode($myAPI->getCategoryCount(),JSON_FORCE_OBJECT
| JSON_UNESCAPED_SLASHES);
break;
case "getProductCount";
echo json_encode($myAPI->getProductCount(),JSON_FORCE_OBJECT |
JSON_UNESCAPED_SLASHES);
break;
case "getCategories":
echo json_encode($myAPI->getCategories($steps, $page,
JSON_FORCE_OBJECT | JSON_UNESCAPED_SLASHES));
break;
case "getProducts":
echo json_encode($myAPI->getProducts($steps, $page,
JSON_FORCE_OBJECT | JSON_UNESCAPED_SLASHES));
break;
default:
echo "Invalid command!";
break;
}
}
}
How can i fix it?
The error not in the abstract class. It's where you actually invoked the a property directly by calling $var->property1 since it's obviously the __get() magic method that's producing the error, which invokes the class get() method. Your controller's registry object needs to have the get() method. You probably don't have the correct registry obj passed into the controller constructor.

How to reference constructed variables from a dynamically added function

How would one rewrite the following ...
class crunch {
private $funcs = [];
public function set($name, $function) {
$this->funcs[$name] = $function;
}
public function call($function, $data=false) {
if (isset($this->funcs[$function]) && is_callable($this->funcs[$function])) {
return $this->funcs[$function]($data);
}
}
}
$db = 'dbhandle';
$crunch = new crunch();
$crunch->set('myfunction', function($data) {
global $db;
echo 'db = '. $db .'<br>'. json_encode( $data );
});
$crunch->call('myfunction', [123,'asd']);
... which correctly outputs ...
db = dbhandle
[123,"asd"]
... to remove the ugly global requirement when using frequently used variables/handles within dynamically added functions?
Normally, I'd define the global on construction as follows, but this understandably fails with the fatal error Uncaught Error: Using $this when not in object context ...
class crunch {
private $db;
private $funcs = [];
public function __construct($db) {
$this->db = $db;
}
public function set($name, $function) {
$this->funcs[$name] = $function;
}
public function call($function, $data=false) {
if (isset($this->funcs[$function]) && is_callable($this->funcs[$function])) {
return $this->funcs[$function]($data);
}
}
}
$db = 'dbhandle';
$crunch = new crunch($db);
$crunch->set('myfunction', function($data) {
echo 'db = '. $this->db .'<br>'. json_encode( $data );
});
$crunch->call('myfunction', [123,'asd']);
What's the cleanest way to accomplish the goal?
EDIT: As #Rajdeep points out, I could pass $db within the $crunch->set() function. But I'd like to avoid this, since each dynamic function could reference anywhere from 0-5 of these private variables, and it would be inelegant to have to call all 5 with every $crunch->set().
Instead of creating a private instance variable $db, you could simply pass this variable to the call() method. Your code should be like this:
class crunch {
private $funcs = [];
public function set($name, $function) {
$this->funcs[$name] = $function;
}
public function call($function, $data=false, $db) {
if (isset($this->funcs[$function]) && is_callable($this->funcs[$function])) {
return $this->funcs[$function]($data, $db);
}
}
}
$db = 'dbhandle';
$crunch = new crunch();
$crunch->set('myfunction', function($data, $db){
echo 'db = '. $db .'<br>'. json_encode( $data );
});
$crunch->call('myfunction', [123,'asd'], $db);
Output:
db = dbhandle
[123,"asd"]
Update(1):
In case you want to access $db as instance variable only, the solution would be like this:
class crunch {
public $db;
private $funcs = [];
public function __construct($db) {
$this->db = $db;
}
public function set($name, $function) {
$this->funcs[$name] = $function;
}
public function call($function, $data=false) {
if (isset($this->funcs[$function]) && is_callable($this->funcs[$function])) {
return $this->funcs[$function]($this, $data);
}
}
}
$db = 'dbhandle';
$crunch = new crunch($db);
$crunch->set('myfunction', function($crunch, $data) {
echo 'db = '. $crunch->db .'<br>'. json_encode( $data );
});
$crunch->call('myfunction', [123,'asd']);
Note that you have to make $db as public member variable, otherwise it would be inaccessible while calling the set() method.

Find object in SplObjectStorage by attached info

I build a chat app using PHP Ratchet.
I store all my connection in SplObjectStorage.
Each connection will have user id that I will attach him by this:
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn)
{
// Store the new connection to send messages to later
$querystring = $conn->WebSocket->request->getQuery();
foreach ($querystring as $value)
{
if($key == "senderId")
$senderId = $value;
}
$this->clients->attach($conn, $senderId);
echo "New connection! ({$conn->resourceId}) senderId({$senderId})\n";
}
When a message arrive I want the fastest way to get the $conn object related to the specific user id.
I can use the trivial foreach like this:
public function onMessage(ConnectionInterface $from, $msg) {
$numRecv = count($this->clients) - 1;
echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n"
, $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');
foreach ($this->clients as $client)
{
if ($from->getInfo() !== $client->getInfo()) {
// do stuff
}
}
I wonder if there is a faster method. Maybe using some function like this:
$conn = $this->clients->getClientWithInfo("WANTED-INFO");
The wanted approach is to void the loop over all my connection in order the send a message to a specific user.
I want to get the connection that is associated with a the user id.
In my opinion there is only one solution to make it work, like you expected => extending the SplObjectStorage class. But then you have two options.
First you can be lazy and add a getClientWithInfo method to the class which find the object for you:
class ConnectionStorageSimple extends SplObjectStorage
{
public function getClientWithInfo($info)
{
$this->rewind();
while ($this->valid()) {
$object = $this->current(); // similar to current($s)
$data = $this->getInfo();
if ($info === $data) {
$this->rewind();
return $object;
}
$this->next();
}
return null;
}
}
$conStorage = new ConnectionStorageSimple();
$con1 = new \stdClass();
$con1->id = 1;
$con2 = new \stdClass();
$con2->id = 2;
$conStorage->attach($con1, 1);
$conStorage->attach($con2, 2);
var_dump($conStorage->getClientWithInfo(1));
var_dump($conStorage->getClientWithInfo(2));
/**
This will output something like that:
class stdClass#2 (1) {
public $id =>
int(1)
}
class stdClass#3 (1) {
public $id =>
int(2)
}
*/
The other option is, that you build your one info-object mapping based on the parent function. This is a little more complex:
<?php
class ConnectionStorage extends SplObjectStorage
{
private $objInfoMapping = array();
public function attach($object, $data = null)
{
if (null !== $data) {
$this->objInfoMapping[$data] = $object;
}
parent::attach($object, $data);
}
public function detach($object)
{
$this->detach($object);
parent::detach($object);
}
public function addAll($storage)
{
$this->addStorage($storage);
parent::addAll($storage);
}
public function removeAll($storage)
{
$this->objInfoMapping = array();
parent::removeAll($storage);
}
public function removeAllExcept($storage)
{
$this->objInfoMapping = array();
$this->addStorage($storage);
parent::removeAllExcept($storage);
}
public function unserialize($serialized)
{
parent::unserialize($serialized);
$this->addStorage($this);
}
public function offsetUnset($object)
{
$this->detach($object);
parent::offsetUnset($object);
}
protected function detachObject($obj)
{
$info = $this[$obj];
if (array_key_exists($info, $this->objInfoMapping)) {
unset($this->objInfoMapping[$info]);
}
}
protected function addStorage(SplObjectStorage $storage)
{
$storage->rewind();
while ($storage->valid()) {
$object = $storage->current(); // similar to current($s)
$data = $storage->getInfo();
$this->objInfoMapping[$data] = $object;
$storage->next();
}
}
public function getClientWithInfo($info)
{
if (array_key_exists($info, $this->objInfoMapping)) {
return $this->objInfoMapping[$info];
}
}
}
$conStorage = new ConnectionStorage();
$con1 = new \stdClass();
$con1->id = 1;
$con2 = new \stdClass();
$con2->id = 2;
$conStorage->attach($con1, 1);
$conStorage->attach($con2, 2);
var_dump($conStorage->getClientWithInfo(1));
var_dump($conStorage->getClientWithInfo(2));
/**
This will also output something like that:
class stdClass#2 (1) {
public $id =>
int(1)
}
class stdClass#3 (1) {
public $id =>
int(2)
}
*/
The main difference between the two classes is, that the second example will perform better on big datasets, because you do not have to iterate over all objects of the storage.
And because you store just object references to the own array, the extra memory consumption should not be so big.
Disclaimer: The classes are just to illustrate the possibilities. The first one should be save to use, but the second one should be tested more
Hope this helps.
this is what I did, see that it's simpler and fast.
namespace mine;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class ws implements MessageComponentInterface {
protected $clients;
protected $clientids;
public function __construct() {
$this->clients = new \SplObjectStorage;
$this->clientids = array();
}
public function multicast($msg) {
foreach ($this->clients as $client) $client->send($msg);
}
public function send_to($to,$msg) {
if (array_key_exists($to, $this->clientids)) $this->clientids[$to]->send($msg);
}
public function onOpen(ConnectionInterface $conn) {
$socket_name = "{$conn->resourceId}#{$conn->WebSocket->request->getHeader('X-Forwarded-For')}";
$this->clients->attach($conn,$socket_name);
$this->clientids[$socket_name] = $conn;
}
public function onMessage(ConnectionInterface $from, $msg) {
}
public function onClose(ConnectionInterface $conn) {
unset($this->clientids[$this->clients[$conn]]);
$this->clients->detach($conn);
}
public function onError(ConnectionInterface $conn, \Exception $e) {
$conn->close();
}
}
this adds 2 functions, one for multicast, and another to message a client by the socket_name which is a string ID (I chose a combination of socket id and ip to stop possible collisions).
so to send to a client:
$ws->send_to(socket_name,message);
obviously $ws is the websocket created at initisation:
$ws = new mine\ws();
$ws_server = new Ratchet\Server\IoServer( new Ratchet\Http\HttpServer( new Ratchet\WebSocket\WsServer( $ws ) ), $socket );

Obtain instance of Zend_Session_Namespace via DI container

I want to retrieve an instance of Zend_Session_Namespace within my models but I don't want them to have a concrete dependency on Zend's implementation (so I can mock it for it testing).
The session instance needs some configuration passed to it at call time. My other dependencies do not and can be configured during the bootstap process.
I have a very basic DI container, borrowed from Fabien Potencier:
class Lib_Container {
protected $services = array();
function __set($id, $service) {
$this->services[$id] = $service;
}
function __get($id) {
if (!isset($this->services[$id])) {
throw new ServiceNotRegisteredException(
"Service '$id' has not been registered"
);
}
if (is_callable($this->services[$id])) {
return $this->services[$id]($this);
}
return $this->services[$id];
}
}
I'm using this to wire up my dependencies:
$container = new Lib_Container;
$container->session = function($c) {
return new Zend_Session_Namespace($c->sessionName);
};
...
I'm using these dependencies within my base model (I don't want my model to know so much about my container configuration):
class Lib_Model {
protected $_container;
protected $_sessionName = 'default';
protected $_sessionInstance;
public function __construct($container) {
$this->_container = $container;
}
public function getDB() {
return $this->_container->database;
}
public function getRequest() {
return $this->_container->request;
}
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns])) {
$this->_container->sessionName = $ns;
$this->_sessionInstance[$ns] = $this->_container->session;
}
return $this->_sessionInstance[$ns];
}
}
This enables my subclasses to retrieve a session instance reasonably conveniently:
class Model_User extends Lib_Model {
protected $_sessionName = 'user';
public function loggedIn() {
$session = $this->getSession();
return ($session && $session->loggedIn) ? true : false;
}
}
Or by passing the session namespace as an argument:
$session = $this->getSession('admin');
However, my Lib_Model::getSession() method is more complex than I would like, and knows too much about my DI container. Ideally want to obtain an instance of Zend_Session_Namespace by calling:
class Lib_Model {
protected $_sessionName = 'default';
protected $_sessionFactory;
...
public function __construct($container) {
$this->_sessionFactory = $container->session;
}
...
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns])) {
$this->_sessionInstance[$ns] = $this->_sessionFactory($ns);
}
return $this->_sessionInstance[$ns];
}
}
I appreciate my DI container is checking if it's services are callable (e.g. anonymous functions) and executing them. If I remove this behaviour the auto-wiring element will crumble?
Any ideas how I can achieve $container->session('my_namespace') to return the equivalent of new Zend_Session_Namespace('my_namespace')?
Update: I thought I was on to something by changing the configuration of my container:
$container->session = function($c) {
$s = function($namespace) {
return new Zend_Session_Namespace($namespace);
};
return $s;
};
So that $container->session would return a function. Updating my Lib_Model class:
Lib_Model {
private $_sessionFactory;
...
public function __construct($container) {
...
$this->_sessionFactory = $container->session;
}
...
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns]))
$this->_sessionInstance[$ns] = $this->_sessionFactory($ns);
return $this->_sessionInstance[$ns];
}
}
Unfortunately this gives me a 500 internal server error :(
I resolved the 500 internal server error by adjusting Lib_Model::getSession() slightly:
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns])) {
$sessionFactory = $this->_session;
$this->_sessionInstance[$ns] = $sessionFactory($ns);
}
return $this->_sessionInstance[$ns];
}
I put together a simple script slowly building up it's complexity until it dawned on me I was calling an undefined method on Lib_Model, though no error message was displayed by PHP running under apache.
$f = function() {
return function($name) {
echo "Hello " . $name . PHP_EOL;
};
};
$hello = $f();
$hello("World");
unset($hello);
// second test
class Container {
protected $services = array();
function __set($id, $service) {
$this->services[$id] = $service;
}
function __get($id) {
if (!isset($this->services[$id])) {
throw new ServiceNotRegisteredException(
"Service '$id' has not been registered"
);
}
if (is_callable($this->services[$id])) {
return $this->services[$id]($this);
}
return $this->services[$id];
}
}
$c = new Container;
$c->h = function() {
return function($name) {
echo "Hello " . $name . PHP_EOL;
};
};
$hello = $c->h;
$hello("Bert");
// third test
class MyTest {
public $attr;
}
$test = new MyTest;
$test->attr = $c->h;
$test->attr("Ernie");
Test output:
$ php -f test.php
Hello World
Hello Bert
PHP Fatal error: Call to undefined method MyTest::attr() in /home/greg/test.php on line 53

Categories