Using namespace in if / else statements - php

I am manipulating the same file to manage two external api classes.
One api class is based on namespaces, the other one is not.
What I would like to do is something like this:
if($api == 'foo'){
require_once('foo.php');
}
if($api == 'bar'){
require_once('bar.php');
use xxxx\TheClass;
}
The problem is that when I do so, the following error message is returned:
Parse error: syntax error, unexpected T_USE in etc...
Question 1: Do I have to use two different files to manage the two classes or is it possible to manage both while using namespaces in the document? From what I see, it does not seem to be.
Question 2: Why namespaces could not be used inside if() statements?
Thank you for your help

Please see Scoping rules for importing
The use keyword must be declared in the outermost scope of a file (the global scope) or inside namespace declarations. This is because the importing is done at compile time and not runtime, so it cannot be block scoped.
All use does is import a symbol name into the current namespace. I would just omit the import and use the fully qualified class name, eg
switch ($api) {
case 'foo' :
require_once('foo.php');
$someVar = new SomeClass();
break;
case 'bar' :
require_once('bar.php');
$someVar = new \xxxx\TheClass();
break;
default :
throw new UnexpectedValueException($api);
}
You can also simply add the use statement to the top of your script. Adding it does not commit you to including any files and it does not require the symbol to be known, eg
use xxxx\TheClass;
switch ($api) {
case 'foo' :
require_once('foo.php');
$someVar = new SomeClass();
break;
case 'bar' :
require_once('bar.php');
$someVar = new TheClass(); // imported above
break;
default :
throw new UnexpectedValueException($api);
}

Use statements should be placed before any executable code (you can have namespaces, classes, functions and constants definitions). Actually it can, just have to be placed unconditionally in some namespace, so no ifs or inside functions. Also don't be afraid of putting use at the top, it does not load any class or instantiate object. it acts only as alias that is used when encountered later during execution.
As for having them in one file, it is possible to have many namespaces and even global namespace in one file:
<?php
namespace
{
class myclass{}
}
namespace mynamespace
{
class myclass{}
}
But I strongly discourage such "management" of code. Each class should have it's own file.

Alright, just confirming what i said was true.. Try something like this :
*Test_1.php*
$API = "test_1";
if ($API === "test"){
}elseif ($API === "test_1"){
require ("test.php");
$API = new daryls\testt;
}
$API->test();
test.php
namespace daryls;
class testt {
public function test(){
echo "Started";
}
}
Running this has worked without a hitch

Another option for API class versioning is to set the classname as a variable conditionally.
// Get class name
switch ($api_version) {
case 'v0':
// No class namespace
$class = 'Handler';
break;
case 'v1':
// Namespaced class
$class = 'API\App\v1\Handler';
break;
case 'v2':
// Namespaced class
$class = 'API\App\v2\Handler';
break;
default:
throw new Exception('Unknown API version: ' . $api_version);
}
// Get class object
$handler = new $class();

Related

Cannot instantiate Object dynamically in PHP - Laravel [duplicate]

The autoloader works when I use it in index.php, but when I create an object within index.php and this object has to create other objects (which are all in the same namespace), it throws the error Uncaught Error: Class 'xxx' not found in (...).
My composer.json looks like this:
{
"autoload": {
"psr-4": {
"pizzaCase\\": "src",
"Connection\\": "src/Connection/",
"Elements\\": "src/Elements/"
}
},
"require": {
"cboden/ratchet": "^0.4"
}
}
My index.php looks like this:
<?php
require_once __DIR__. '/vendor/autoload.php';
require_once __DIR__."/src/config.php";
use Connection\Database;
use Elements\Form;
use Elements\FormElement;
use Elements\FormElementRadio;
// Database::init();
$form = new Form();
$data["options"] = "soemthing, something else";
$form->addElement("", "pizza", "", "Choose pizza", "radio", $data);
?>
In the addElement method I then create an object which is also within the src/Elements/ namespace, but it throws the error mentioned above.
The body of my addElement method looks like this:
<?php
namespace Elements;
class Form
{
public static $leftSize = 3;
protected $elements = [];
public function addElement($table, $name, $value, $label=false, $type = false, $data = false)
{
$type = ucfirst($type);
$class = "FormElement{$type}";
//FAILS HERE
if(class_exists($class))
{
//CLASS EXISTS, CREATE OBJECT FROM RESPECTIVE CLASS
$form = new $class($table, $name, $value, $label, $type, $data);
$this->elements[$name] = $form;
}
}
}
What am I doing wrong (or missing)? How come the autoloader can autoload it from index.php, but the object I create cannot create other objects without autoloader failing?
The difference is not to do with where the code is being run; the difference is that the failing code is trying to choose which class to load dynamically.
In PHP, namespaces are essentially a compile-time feature: before any of your code is run, the compiler looks at all references to class names which don't start with \, and prefixes them with the current namespace, or according to rules you've specified with use statements. When the code runs, the current namespace, and use statements, aren't visible at all.
When you specify a class name dynamically, the compiler just sees a string, not a class name, so leaves it alone. Then when the code runs, the class name looked up is assumed to be fully specified, not relative to the current namespace or use statements.
So the solution is simple - specify the full namespace when creating the dynamic class name:
$class = "Elements\FormElement{$type}";
You can also use the magic constant __NAMESPACE__ to have the compiler substitute the current namespace name for you (obviously, this still won't account for any use statements):
$class = __NAMESPACE__ . "\FormElement{$type}";
Alternatively, if you have a specific set of classes you are choosing between, you can use the ::class syntax to generate a string at compile time, based on the current namespace and any use statements in effect:
$mapTypeToClassName = [
'Radio' => FormElementRadio::class, // expands to 'Elements\FormElementRadio'
'Select' => FormElementSelect::class,
// etc
];
$class = $mapTypeToClassName[$type];
It could be because you’re having multiple namespaces for the src directory.
Usually you would just create a namespace for src like this
“psr-4": {
"PizzaCase\\": "src"
}
And then just use PizzaCase\Elements and PizzaCase\Connections as namespaces

Why the "use" command in PHP not working with include?

I have this script:
<?php
error_reporting(E_ALL);
require 'vendor/autoload.php';
use Church\Config;
use Church\SQLiteConnection;
use Church\Template;
use Church\User;
$ERROR = "";
if(isset($_POST['username']) && isset($_POST['username']))
{
$login = new User((new SQLiteConnection())->connect());
if($login->loginUser($_POST['username'], $_POST['password']))
{
}
else {
$ERROR = "login";
}
}
if(isset($_GET['go']))
{
}
else {
if (!file_exists(Config::PATH_TO_SQLITE_FILE)) {
include('init/install.php');
}
}
When I make it with this part:
include('init/install.php');
instead of this:
$tpl = new Template('templates/install.tpl');
$tpl->set('HEADER', $tpl->getFile('templates/header.tpl'));
$tpl->set('FOOTER', $tpl->getFile('templates/footer.tpl'));
$tpl->set('APP_NAME', Config::APP_NAME);
$tpl->set('APP_VERSION', Config::APP_VERSION);
$tpl->set('BASE_URL', $_SERVER['PHP_SELF']);
$tpl->render();
I get this error:
Fatal error: Uncaught Error: Class 'Template' not found in
I do not understand why it is working without include but with include is the auto loading not working. What did I miss?
Take a look at the docs here: https://www.php.net/manual/en/language.namespaces.importing.php
The use keyword must be declared in the outermost scope of a file (the global scope) or inside namespace declarations. This is because the importing is done at compile time and not runtime, so it cannot be block scoped. The following example will show an illegal use of the use keyword:
And then:
Importing rules are per file basis, meaning included files will NOT inherit the parent file's importing rules.
The conclusion is that you can't do what you want to do. You need to import (aka use) in your included file too.

How to use a global constant instead of a class constant in PHP version 5.6

I'm using Monolog to create my app's logging system. In the core app file, after I create a new Monolog object, I need to select the log level that I want to print in the log file. I want to use a global constant LOG_LEVEL which could be 'DEBUG', 'INFO', etc. I need the Monolog class to treat its value as a class constant.
// content of config.php
// Here I declare the constants in a separate file called 'config.php'
define("LOG_FILE", "patch/to/my/log.log");
define("LOG_LEVEL", "ERROR");
// content of app.php
require 'config.php';
require 'vendor/autoload.php';
$container['logger'] = function($c) {
$logger = new \Monolog\Logger('logger');
error_log('log level ' . LOG_LEVEL); // prints 'log level ERROR'
$fileHandler = new \Monolog\Handler\StreamHandler(LOG_FILE, $logger::LOG_LEVEL); // here I get the error 'Undefined class constant LOG_LEVEL'
//the normal syntax would be '$logger::ERROR' in this case and that works fine
$logger->pushHandler($fileHandler);
return $logger;
};
I need the 'LOG_LEVEL' constant to be used as 'ERROR' by the monolog class, not as 'LOG_LEVEL'. What am I doing wrong here, been searching an answer for hours now without any luck.
You are now doing $logger::LOG_LEVEL, which is taking the 'LOG_LEVEL' out of the class whichever $logger is (in this case a \Monolog\Logger). That doesn't have a static variable named LOG_LEVEL, thus you get the undefined.
You have just have 'LOG_LEVEL' defined, out of any class, so:
$fileHandler = new \Monolog\Handler\StreamHandler(LOG_FILE, LOG_LEVEL);
Fancy solution:
You could do a static class and include that in your main page:
Class CONFIG {
public static $LOG_LEVEL = 'default Value';
}
// Then you can use this anywhere:
CONFIG::$LOG_LEVEL
$fileHandler = new \Monolog\Handler\StreamHandler(LOG_FILE, CONFIG::$LOG_LEVEL);
The advantage of this is having only one file for configs, not scattered across all kinds of files, which'll become very annoying very fast.
Make a static class and include that...
class GLOBALCONF{
public static $VALUE= 'Something in here';
}
// Use it where you want
GLOBALCONF::$VALUE
You're making this more complicated than it needs to be. Monolog has a function to convert an error level as as string to its own internal value. Just change your code to this:
$fileHandler = new \Monolog\Handler\StreamHandler(LOG_FILE, $logger::toMonologLevel(LOG_LEVEL));
You can also use Logger::getLevels() like the following:
$log_level = $logger->getLevels()[LOG_LEVEL];
$fileHandler = new ...StreamHandler(LOG_FILE, $log_level);

Namespaced class not found

I have the following which works just fine:
use frontend\models\modules\shipping\Usps;
$shipping = new Usps;
If however I use the following it throws the error: Class 'Usps' not found
use frontend\models\modules\shippingl\Usps;
I am loading classes dynamically, thus I cannot use the namespace when calling new $class, eg. I might also have:
use frontend\models\modules\payment\Usps;
$class = 'Usps';
$shipping = new $class;
How can I make this work using a variable? If I don't use namespaces/autoload and just include the class file it works just fine.
This question is not a duplicate, the referenced question/answers do not declare a 'use'.
This is the only way I see this working without security issues and tons of errors, especially if $class coming from user input.
I added a "Ups" class so it makes a little more sense.
use frontend\models\modules\payment\Usps;
use frontend\models\modules\payment\Ups;
if($class == 'Usps') {
$shipping = new Usps;
} else if($class == 'Ups') {
$shipping = new Ups;
} else {
// No valid class found - handle accordingly
}

PHP Namespace and global variable issue within classes

I am stuck with this confusion where I don't understand why my global $error under my HelperClass() returns empty, where I could verify that $class->error is indeed filled up with data earlier on.
Is there some sort of issues with namespace in this case that I am not aware about? Please give me some pointers.
Here are some of the codes that are relevant.
Under Main file
namespace Core;
$class = new ControllerClass();
$error = $class->error;
// verified that $error prints correctly here
include ViewFile.php;
Under ViewFile.php
$helper = new HelperClass();
// __autoload function took care of the include
Under HelperClass:
namespace Core\Skeleton;
class HelperClass {
public function __construct() {
global $error;
// $error != $class->error as defined earlier
// $error is empty here
}
If you're using an autoloader or include your classes from within another helper function, then the $error variable was never declared in the 'global' scope. It ended up in some local, and got disposed.
Declare it shared right before you assign it a value.
namespace Core;
$class = new ControllerClass();
global $error;
$error = $class->error;
Also while there is nothing wrong with shared variables per se. The name $error seems slightly too generic. Maybe you can up with a less ambigious or more structured exchange variable. $GLOBALS["/var/log"]["controller_error"] or something arrayish.

Categories