I have a settings.php file that looks something like this:
$user="adam";
$pass="something";
then I have a class called funcs.php, in which I have a require "../settings.php" on top, but I don't know how to get the scope of "$user" into my methods...
I'm sure this is an easy fix, but I've been googling in circles for a while with no luck.
My class looks something like this:
<?php
require_once __DIR__."/../settings.php";
class Funcs
{
public function something()
{
// do stuff
return $user;
}
}
you can use the return construct inside file which can return anything, but if you want to return multiple values, it's best to use an array.
settings.php
return array(
'user' => 'adam',
'pass' => 'something'
);
file.php
$settings = require __DIR__.'settings.php';
echo $settings['user']; // adam
To pass everything returned from the file into a class:
class MyClass {
public function __construct(array $array) {
foreach($array as $key => $value) {
$this->{$key} = $value;
}
}
}
$class = new MyClass(require __DIR__ . '/settings.php');
var_dump($class);
/*
class MyClass#1 (2) {
public $user =>
string(4) "adam"
public $pass =>
string(9) "something"
}
*/
I think the issue is that these are global variables used inside a function or class scope.
If you want to access these from within a function scope you could use the "global" keyword, for example:
public function foo() {
global $user;
echo $user;
}
However – using global (and global variables in general) is usually considered pretty smelly these days.
Some alternatives:
Pass the configuration into the constructor of your class, therefore:
$config = array(
'user' => 'adam',
'pass' => 'something'
);
class MyConsumer {
private $config;
public function __construct($config) {
$this->config = $config;
}
}
$instance = new MyConsumer($config);
This is usually termed "dependency injection", because your config can be easily substituted for something else depending on the context (such as in unit tests).
An alternative is to have a singleton class which encapsulates your config, which you'd access something like (not tested but you get the idea):
// production_config.php
return array(
'user' => 'adam',
'password' => 'something'
);
// config.php
class Config {
protected $config;
protected static $instance = null;
protected function __construct() {
$this->config = require('production_config.php');
}
protected function getKey($key) {
if(isset($this->config[$key])) {
return $this->config[$key];
}
// Possibly throw exception or something else if not found
}
public static function get($key) {
if(self::$instance === null) {
self::$instance = new self();
}
return self::$instance->getKey($key);
}
}
So you can go:
$user = Config::get('user');
This is perhaps easier than passing your config into every class, but it's more difficult to substitute your config for testing purposes.
Related
While i was working on a school project I ran into a problem.
I have made an Router class where it get the right files, but I am getting the following error:
Fatal error: Method mvc\App::__toString() must not throw an exception, caught Error: Call to a member function getHTML() on string in /opt/lampp/htdocs/School/testPHP/mvc/public/index.php on line 0
The echo returns the correct file path so that is not the issue I'm running into.
I think the issue is because I try to execute an function into a string.
Does anyone know how I can fix my issue?
App Class:
<?php
namespace mvc;
class App{
private $router;
public function __construct(){
$this->router = new \mvc\Router();
}
public function __toString(){
try {
echo $this->router->getView(); //this returns the correct file path
return $this->router->getView()->getHTML();
} catch (Exception $e) {
return $e.getMessage;
}
}
}
?>
Router.php:
<?php
namespace mvc;
class Router{
private $route;
private $view;
private $controller;
private $model;
public function __construct(){
require_once(LOCAL_ROOT. "php/Routes.php");
if (isset($_GET['route'])){
$this->route = explode("/" , $_GET['route']);
}
$route = isset($routes[$this->getRoute()])? $this->getRoute() : DEFAULT_ROUTE;
$this->controller = "\\controllers\\". $routes[$route]['controller'];
$this->view = "\\views\\". $routes[$route]['view'];
// $model = "\\models\\". $routes[$route]['model'];
}
private function getRoute(){
return count($this->route) > 0 ? $this->route[0] : DEFAULT_ROUTE;
}
public function getView(){
return $this->view;
}
}
?>
Routes.php
<?php
define("DEFAULT_ROUTE", "home");
$routes = array(
"home" => array(
"view" => "HomeView",
"controller" => "HomeController",
),
"form" => array(
"view" => "FormView",
"controller" => "FormController",
),
"test" => array(
"view" => "TestView",
"controller" => "TestController",
),
)
?>
TestView.php
<?php
namespace views;
class TestView extends \mvc\View{
public function getHTML(){
// return 'dit is testView';
$klik = $this->controller->getGetData("klik");
$output = "";
$output .= "<h1>".$klik++ ."</h1>";
$output .= "klik";
$output .= "<br>";
return $output;
}
}
?>
The Problem:
Okay so the problem is that you're making a call to a function from a string.
This is the getView method in the Router class:
public function getView(){
return $this->view;
}
This method returns a string:
$this->view = "\\views\\". $routes[$route]['view'];
You are then trying to call a method from this string:
return $this->router->getView()->getHTML();
The Potential Solution:
Obviously you're trying to access the getHTML method in the TestView class, so without knowing more about your setup, it's hard to get exact, but I'll take a guess at the following and you can guide me in the right direction in the comments.
If you change the constructor in your App class to this:
public function __construct(){
$this->router = new \mvc\Router();
$this->testView = new \views\TestView();
}
Then you can change your __toString method to the following:
echo $this->router->getView(); //this returns the correct file path
return $this->testView->getHTML();
I'm not sure why you'd echo and then return, usually it's one or the other, but if you elaborate more on your expected output I can help you some more, or hopefully my explanation has given you enough to work from.
Solution:
After reading your comments, it looks like you want to instantiate the class from the result of the getView method, you can do this by setting the result into a variable and calling a new class from that string and THEN calling the method from that class.
$class = $this->router->getView();
$class = new $class;
return $class->getHTML();
I have recently moved a large php application from using mssql_ functions to the PDO function using the mssql driver.
I wrote a simple library that allows drop in replacement. It all seems to work pretty well considering.
However one thing that is a bit annoying is default format of numbers and particularly numbers defined as money in the database.
Most of my smarty template pages previous simply output the number as it came from the database so someones balance might be show as
125.00
however since changing to PDO this is returned as
125.0000
This is a little annoying and off putting, but obviously not the end of the world.
My Question. Is there a workaround / trick / formatting Constant or method that I can use to get PDO to format values differently, or do I need to go an manually set the format for every number in every template throughout my app?
So basically, what I'd do is create models that represent a result-set for each table, and use PDO::FETCH_CLASS to load the data into instances of the corresponding class. For example:
class UserTable //extends AbstractTable <-- see below
{
protected $id = null;
protected $name = null;
protected $email = null;
protected $money = null;
}
Then add getters and setters that format/validate the data accordingly eg:
public function getMoney()
{
return sprintf('%.2f', $this->money);//check if not null first, obviously
}
Next, have an abstract class for these models, and implement the ArrayAccess interface in there. For example, using a simple mapping array:
protected $getterMap = [
'email' => 'getEmail',
'id' => 'getId',
'money' => 'getMoney',
];
Define a tailor-made map in each child, then have the abstract class use it like so:
//in abstract class AbstracTable implements ArrayAccess
public function offsetGet($offset)
{
if (!isset($this->getterMap[$offset])) {
throw new RuntimeException(
sprintf('%s not a member of %s', $offset, get_class($this));
);
}
$getter = $this->getterMap[$offset];
return $this->{$getter}();//use the getter, it formats the data!
}
Do something similar for all 4 methods in the interface, and now you can use this:
$row = $stmt->fetch(PDO::FETCH_CLASS, 'User');
$row['money'];//will call getMoney, and return the formatted number
A more complete example:
abstract class AbstractTable implements ArrayAccess
{
protected $id = null;//very likely to be defined in all tables
protected $getterMap = [
'id' => 'getId',
];
protected $setterMap = [
'id' => 'setId',
];
//force child classes to define a constructor, which sets up the getter/setter maps
abstract public function __construct();
public offsetExists($offset)
{
return isset($this->getterMap[$offset]);
//optionally, check if value if not null: isset($arr['keyWithNullVal']) returns null, too:
return isset($this->getterMap[$offset]) && $this->{$offset} !== null;
}
public offsetGet ( mixed $offset )
{
if (!isset($this->getterMap[$offset])) {
throw new RuntimeException('member does not exist');
}
$getter = $this->getterMap[$offset];
return $this->{$getter}();
}
public offsetSet($offset, $value )
{
if (!isset($this->setterMap[$offset])) {
throw new RuntimeException('Trying to set non-existing member');
}
$setter = $this->setterMap[$offset];
$this->{$setter}($value);
}
public offsetUnset ($offset)
{
//same as setter, but call:
//or just leave blank
$this->{$setter}(null);
}
}
class UserTable extends AbstractTable
{
//protected $id = null; in parent already
protected $name = null;
protected $email = null;
protected $money = null;
public function __construct()
{
$fields = [
'name' => 'etName',
'email' => 'etEmail',
'money' => 'etMoney',
];
foreach ($fields as $name => $method) {
$this->getterMap[$name] = 'g' . $method;
$this->setterMap[$name] = 's' . $method;
}
}
}
Obviously, you'll have to write the getters and setters for all the fields. Not to worry, though: most IDE's will helpfully generate the getters and setters for predefined properties at the click of a button
Can anyone tell me the advantage of using the classmap option within PHP Soapclient? Maybe with some practical examples?
The classmap option can be used to map some WSDL types to PHP classes.
Example,
class MyLoginResult {
protected $serverUrl;
protected $sessionId;
public function getServerUrl()
{
return $this->serverUrl;
}
public function getSessionId()
{
return $this->sessionId;
}
public function getServerInstance()
{
$match = preg_match(
'/https:\/\/(?<instance>[^-]+)\.example\.com/',
$this->serverUrl,
$matches
);
return $matches['instance'];
}
}
$client = new SoapClient("books.wsdl",
array('classmap' => array('LoginResult' => "MyLoginResult")));
$loginResult = $client->getLoginResult();
$instance = $loginResult->getServerInstance();
As addition to the comment by hoangthienan, I would show one more advantage when using a mapped class.
E.g. you could extend the class by a __set() method, that would be triggered when the SoapClient passes its data to the mapped class (you should know, the method will not be triggered if your property is public).
In that case you can alternate the data passed from SoapClient before you assign it to your Data-Class.
class MyLoginResult {
protected $serverUrl;
protected $sessionId;
private $is_logged_in;
public function __set($name, $value) {
if ($name == 'login_status') {
$this->is_logged_in = ($value == 'logged_in') ? true : false;
} else {
$this->$name = $value;
}
}
public function loginSuccessfull() {
return $this->is_logged_in;
}
// class code from hoangthienan
}
e.g. in this example we get a string from Soap, but we store a bool-value in our class.
You could use this for other changes to e.g. if you like to store your internal variables in a array instead of using direct properties.
In my application I have a Config object which my service layer and views have as a class property. Any time I want a config value I have to do
$configValue = $this->config->setting('setting.name');
What I want to be able to do each time I want a setting is
$configValue = $this->config('setting.name');
To accomplish this I have now put this code within my AbstractService and AbstractView I have
protected function config($setting) {
return $this->config->setting($setting);
}
It is only a tiny little bit of code duplication but I would still prefer to not have it so would it be the right choice to use traits here?
Perhaps you're looking for the magic __invoke function (requires 5.3+, but since you use traits I assume you have this):
<?php
class Config {
private $config = array(
'ip' => '127.0.0.1',
'port' => 12345
);
public function setting($option) {
if (!empty($this->config[$option])) {
return $this->config[$option];
}
return false;
}
public function __invoke($option) {
return $this->setting($option);
}
}
$config = new Config();
var_dump($config('ip')); //127.0.0.1
?>
DEMO
I often use properties in my classes that store an array of options. I'd like to be able to somehow merge those options from defaults declared in a parent class.
I demonstrated with some code.
class A
{
public $options = array('display'=>false,'name'=>'John');
}
class B extends A
{
public $options = array('name'=>'Mathew');
}
Now when I create B, then I'd like $options to contain a merged array from A::options
What happens now is this.
$b = new B();
print_r($b);
array('name'=>'Mathew');
I would like something like this using array_merge_recursive().
array('display'=>false,'name'=>'Mathew');
Maybe it's something I could do in the constructor?
Is it possible to make this a behavior of class A? So that I don't always have to implement the same code in all subclasses.
Could I use reflection to auto find array properties in both classes and merge them?
In addition to the previous answers, another approach that may be suited for certain cases would be to use PHP Reflection or built-in class functions. Here is a basic example using the latter:
class Organism
{
public $settings;
public $defaults = [
'living' => true,
'neocortex' => false,
];
public function __construct($options = [])
{
$class = get_called_class();
while ($class = get_parent_class($class)) {
$this->defaults += get_class_vars($class)['defaults'];
}
$this->settings = $options + $this->defaults;
}
}
class Animal extends Organism
{
public $defaults = [
'motile' => true,
];
}
class Mammal extends Animal
{
public $defaults = [
'neocortex' => true,
];
}
$fish = new Animal();
print_r($fish->settings); // motile: true, living: true, neocortex: false
$human = new Mammal(['speech' => true]);
print_r($human->settings); // motile: true, living: true, neocortex: true, speech: true
I realize I changed your interface from a public variable to a method, but maybe it works for you. Beware, adding a naive setOps($ops) method may work unexpected if you allow the parent ops to continue to be merged in.
class A
{
private $ops = array('display'=>false, 'name'=>'John');
public function getops() { return $this->ops; }
}
class B extends A
{
private $ops = array('name'=>'Mathew');
public function getops() { return array_merge(parent::getOps(), $this->ops); }
}
class c extends B
{
private $ops = array('c'=>'c');
public function getops() { return array_merge(parent::getOps(), $this->ops); }
}
$c = new C();
print_r($c->getops());
out:
Array
(
[display] =>
[name] => Mathew
[c] => c
)
You can use a simple pattern like so:
abstract class Parent {
protected $_settings = array();
protected $_defaultSettings = array(
'foo' => 'bar'
);
public __construct($settings = array()) {
$this->_settings = $settings + $this->_defaultSettings;
}
}
In this way it's easily possible to modify the defaults applied in child classes:
class Child extends Parent {
protected $_defaultSettings = array(
'something' => 'different';
);
}
Or to apply something more complex:
class OtherChild extends Parent {
function __construct($settings = array()) {
$this->_defaultSettings = Configure::read('OtherChild');
return parent::__construct($settings);
}
}
Merging variables
Cake does come with a function for merging variables. It's used for controller properties such as components, helpers etc. But be careful applying this function to none trivial arrays - you may find that it doesn't quite do what you want/expect.