I am writing a program which processes geographical coordinates of a city and I want to randomly select my city in each run; the process for all cities are the same.
I have a PHP class for each city which contains geographical coordinates of it; for example:
<?php
namespace Utility\Locations;
class Tehran
{
const MIN_LAT = 35.325;
const MAX_LAT = 35.390;
const MIN_LNG = 51.165;
const MAX_LNG = 51.230;
}
In another PHP file I make use of the class as:
use Utility\Locations\Tehran;
use Utility\Locations\Karaj;
...
protected function MyProcessingMethod () {
...
$city = Faker::create()->randomElement(array("Tehran", "Karaj"));
echo $city::MIN_LAT;
...
}
...
If it helps, the above mentioned file is a CodeCeption Cest and MyProcessingMethod is used as a DataProvider. When I run my tests using codecept run scenarios/MyCest.php, I get this error:
PHP Fatal error: Uncaught Error: Class 'Tehran' not found in /home/zeinab/PhpstormProjects/test/scenarios/MyCest.php:190
Stack trace:
#0 [internal function]: MyCest->MyProcessingMethod()
#1 /home/zeinab/PhpstormProjects/test/vendor/codeception/codeception/src/Codeception/Util/ReflectionHelper.php(47): ReflectionMethod->invokeArgs(Object(MyCest), Array)
#2 /home/zeinab/PhpstormProjects/test/vendor/codeception/codeception/src/Codeception/Test/Loader/Cest.php(65): Codeception\Util\ReflectionHelper::invokePrivateMethod(Object(MyCest), Object(ReflectionMethod))
#3 /home/zeinab/PhpstormProjects/test/vendor/codeception/codeception/src/Codeception/Test/Loader.php(109): Codeception\Test\Loader\Cest->loadTests('/home/zeinab/Ph...')
#4 /home/zeinab/PhpstormPro in /home/zeinab/PhpstormProjects/test/scenarios/MyCest.php on line 190
I've read PHP official documentations on this type of usage, but all examples there were using the classes that are defined inside destination file.
I also tried this:
$city = Faker::create()->randomElement(array(Tehran::class, Karaj::class));
echo $city::MIN_LAT;
But I got the same error.
Is there anyway to do what I want to?
EDIT 1:
I have put the path to the classes in psr-4 tag of my composer.json:
"autoload": {
"psr-4": {
"Utility\\": "Utility/"
}
}
It seems there is no problem in loading the classes, since using them directly works fine. For example the following code is doing great:
echo Tehran::MIN_LAT;
I created this code snippet for example purposes - so it may not fully reflect your methods but the answer is the principle :)
<?php
# create our city classes
class Brighton
{
const X = 1;
const Y = 2;
}
class London
{
const X = 3;
const Y = 4;
}
# create our get city class
class Foo
{
public function getCity()
{
$possibleCities = ['Brighton', 'London', 'Birmingham']; # add a city that's not a class for testing
$city = rand(0, count($possibleCities) -1);
return $possibleCities[$city];
}
}
# init foo
$foo = new Foo();
$city = $foo->getCity(); # get city
# check if class exists, if it does, init it
$cityClass = (class_exists($city) ? new $city() : null);
# check if class is empty - if it isn't echo CONST X
echo (!empty($cityClass) ? $cityClass::X : $city. ' class not found');
How to include the classes (if they're in separate files):
Imagine the tree is:
|-app
|-----core
|---------city
|-------------Brighton.php
|-------------London.php
|-----Foo.php
In Foo.php
<?php
require_once 'city/Brighton.php';
require_once 'city/London.php';
use City\Brighton;
use City\London;
# etc. etc
And then Brighton.php and London.php have the namespace of City:
<?php
namespace City;
class Brighton
{} # etc
add loader to first line of your code :
function loader($className)
{
$fileName = str_replace('\\', '/', $className) . '.php';
require_once $fileName;
}
spl_autoload_register('loader');
then use namespaces like directory structures :
Ahvaz.php in app/core/city directory
namespace app\core\city;
class Ahvaz
{
//...
}
when you load a class Ahvaz.php like this
$var = new Ahvaz();
you need to add use app\core\city;
then you have :
use app\core\city;
$var = new Ahvaz();
it will include Ahvaz.php from app/core/city directory
Related
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
I'm stuck on an error that I can't solve, I need help please.
Attempted to load class "MockStorageStrategy" from namespace "MangoPay\Tests\Mocks".
Did you forget a "use" statement for another namespace?
my code:
<?php
namespace App\Service;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use App\Entity\User;
use MangoPay;
use MangoPay\Tests\Mocks\MockStorageStrategy;
class CallApiService
{
private $mangoPayApi;
private $client;
public function __construct(HttpClientInterface $httpClient)
{
$this->client = $httpClient;
$this->mangoPayApi = new MangoPay\MangoPayApi();
$this->mangoPayApi->Config->ClientId = $_ENV['CLIENT_ID'];
$this->mangoPayApi->Config->ClientPassword = $_ENV['API_KEY'];
// $this->mangoPayApi->Config->TemporaryFolder = '/some/path/';
$this->mangoPayApi->OAuthTokenManager->RegisterCustomStorageStrategy(new MockStorageStrategy());
//$this->mangoPayApi->Config->BaseUrl = 'https://api.sandbox.mangopay.com';
}
public function createProfilMango($form)
{
$userMango = $this->client->request(
'POST',
'https://api.sandbox.mangopay.com/v2.01/' . '%env(CLIENT_ID)%' . '/users/natural',
[
$UserNatural = new MangoPay\UserNatural(),
$UserNatural->FirstName = $form['firstname']->getData(),
$UserNatural->LastName = $form['lastname']->getData(),
$UserNatural->Email = $form['email']->getData(),
$UserNatural->Address = new \MangoPay\Address(),
$UserNatural->Address->AddressLine1 = $form['streetNumber']->getData() . $form['address']->getData(),
$UserNatural->Address->AddressLine2 = "",
$UserNatural->Address->City = $form['city']->getData(),
$UserNatural->Address->Region = "",
$UserNatural->Address->PostalCode = $form['zipCode']->getData(),
$UserNatural->Address->Country = "FR",
$UserNatural->Birthday = $form['birthday']->getData(),
$UserNatural->Nationality = $form['nationality']->getData(),
$UserNatural->CountryOfResidence = "FR",
$Result = $this->mangoPayApi->Users->Create($UserNatural),
]
);
return $userMango;
}
}
The namespace has been checked and it is correct, concerning the dependencies the http-client and mangopay/php-sdk-v2 have been installed.
Using classes from a Test namespace are sometimes not added to the autoloader - as you should not use them in your application. A look at that package's composer.json shows this: the namespace MangoPay is routed to the folder MangoPay (see autoload for this), while the class you want to use is stored in another folder and loaded only through autoload-dev. This section is not evaluated in case you are solely using this package in your own application.
If you really want to use that class MockStorageStrategy (which is only provided for the package's internal test suite, not for being used by the application!), you should copy it to your own application namespace.
Class not found, apparently. I've tried various things but nothing works.
Composer:
"autoload": {
"psr-4": {
"App\\": "application/"
}
}
File structure:
https://i.imgur.com/h9wOEqI.png
<?php
namespace App\Library\Classes;
defined('START') or exit('We couldn\'t process your request right now.');
class Application
{
private static $libraries = array();
public static function get($library) {
if (isset(self::$libraries[$library]) && isset(self::$classes[$library])) {
return self::$libraries[$library];
}
$fixedLibrary = str_replace('.', '/', $library);
$file = ROOT . '/application/library/classes/' . strtolower($fixedLibrary) . '.php';
self::$libraries[$library] = $library;
$declared = get_declared_classes();
$workingClass = end($declared);
self::$libraries[$library] = new $workingClass();
return self::$libraries[$library];
}
}
?>
Error is on this line:
Application::get('test')->test();
Yet, if I change it to this, it works:
include ROOT . '/application/Library/Application.php';
App\Library\Classes\Application::get('test')->test();
The PSR4 is not built-in part or PHP, you need an implementation of autoloader to use this standard such as provided by the Composer.
When you install or update depedencies, composer generates the relevant code of autoloading, but you can directly update it by the command dump-autoload, as #jibsteroos said. Next you should explicitly include the file vendor/autoload.php in the entry point of your application.
Also, error message says about class Application, but you should add the use statement at first:
use App\Library\Classes\Application;
Application::get('test')->test();
Or use the fully qualified class name (class name with namespace prefix):
\App\Library\Classes\Application::get('test')->test();
I"m sorry now but I don't know if I can get all the details possible to make an educated guess on what exactly is going on.
I'm trying to check to see if a class exists, if it doesn't I need to create the class. Some classes have sub classes (namespaces).
namespace {
class wattsjus {
}
}
namespace wattsjus {
class OtherClass {
}
}
so whether I __autoload wattsjus or wattsjus\OtherClass I'm loading both classes.
I can tell that up until the point I load the classes they are not there if I try and autoload the 'inner class'. After I load the class I get an error that the class is already loaded. Yet I can print text just before that point and that line is never reached.
I am using class_exists to make sure the wattsjus class is not loaded. Before I load the class this is true (loaded). yet if I try to make an instance of wattsjus it cannot be found.
I'm using the following code to check where the class was created and it's stating a line number that is never reached at this point. I have an print statement before this line to be sure.
$reflector = new ReflectionClass('wattsjus');
echo $reflector->getFileName();
A great mystery to me as to how code could have been reached without executing the previous line.
Edit, Warning this is ugly code:
function __autoload($class_name) {
echo 'in';
$parts = explode('\\',$class_name);
if($class_name == $_GET['u5u'] || $parts[0] == $_GET['u5u']) {
$cl = class_exists($_GET['u5u'], false);
echo $cl ? 'yes' : 'no';
if(!$cl) {
if($_GET['e5e'] != 'Prod') { $env = $_GET['e5e']; }
$q = "SELECT f.`Name`, f.`$env"."ParameterList` `ParameterList` FROM `Function` f WHERE f.UserId = ".SiteUserId." AND `Status` = 'A' ORDER BY INSTR(f.`Name`, '\\\\') > 0, f.`Name`";
$results = DataObject::GetDBObject()->Query($q, 'wattsjus_json');
$class = "namespace { class $_GET[u5u] {";
$class .= GetFunctions($results);
$class .= '}}';
echo 'I\'m not here yet';
eval($class);
} else {
$reflector = new ReflectionClass('wattsjus');
echo $reflector->getFileName();
}
here the output that is produced trying to get OtherClass is:
inyes/home4/wattsjus/public_html/ajson/global_base.php(25) : eval()'d code<br />
<b>Fatal error</b>: Class 'wattsjus\OtherClass' not found in <b>/home4/wattsjus/public_html/ajson/global_base.php(112) : runtime-created function</b> on line <b>1</b><br />
so to me this is saying that the class wattsjus already exists which should include OtherClass in the namespace. Though it cannot be found. If I let it keep going and create wattsjus again it will say the class has already been defined. Very confusing I know.
I changed the structure of the classes to use "namespace wattsjus\OtherClass" rather than make a OtherClass class and for some reason PHP is less confused by this. Really weird glitch/bug I'd have to say.
namespace {
class wattsjus {
}
}
namespace wattsjus\OtherClass {
}
I am fiddling around with autoloading, and trying to make a file structure that abides to the PSR-0 Standard. However I am getting this error:
Warning: require(GetMatchHistory\api.php): failed to open stream: No such file or directory in C:\xampp\htdocs\Test\Test.php on line 15
Fatal error: require(): Failed opening required 'GetMatchHistory\api.php' (include_path='.;C:\xampp\php\PEAR') in C:\xampp\htdocs\Test\Test.php on line 15
And here is my file structure:
I am using Test PHP with this autoloader function:
function autoload($className)
{
$className = ltrim($className, '\\');
$fileName = '';
$namespace = '';
if ($lastNsPos = strrpos($className, '\\')) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
require $fileName;
}
spl_autoload_register('autoload');
$Query = new GetMatchHistory_api();
This function is copy-pasted from the suggested PSR-0 function here
Am I misunderstanding with regards to how the structure should be? The class within "api.php" is GetMatchHistory_api
Edit: Another problem
I have used the suggested answer by MrCode, however now I am having an issue where the autoloader wont load a class that is in another directory.
use Classes\Queries\History\GetMatchHistoryAPI;
use Classes\Queries\History\GetMatchHistoryBASE;
use Classes\Utilities\send;
When I am calling the send class, from within a function inside GetMatchHistoryAPI, I am receiving the error:
Warning: require(Classes\Queries\History\send.php): failed to open stream: No such file or directory in C:\xampp\htdocs\Test\Test.php on line 15
However, as you can tell from the above image, the send class is not in that file path. Why is this error occurring?
Based on that structure, you would need to rename the class to Classes_Queries_GetMatchHistory_api and change the code that instantiates it to:
$Query = new Classes_Queries_GetMatchHistory_api();
The reason for this is your Test.php resides at the root and the api class is in the directory Classes/Queries/GetMatchHistory.
Example using namespaces instead of the underscore method:
api.php:
namespace Classes\Queries\GetMatchHistory;
class api
{
}
Test.php:
spl_autoload_register('autoload');
$Query = new Classes\Queries\GetMatchHistory\api();
Or using use:
use Classes\Queries\GetMatchHistory\api;
spl_autoload_register('autoload');
$Query = new api();
To address your comment:
I was going to have the same class names, but under a different folder (so GetMatchHistory - api.php and GetMatchDetails - api.php). I guess this would make it too ambiguous when calling the classes however
Namespaces are designed to solve this very problem. Namespaces allow you to have classes with the same name (but in different namespaces) and avoid any conflicts.
As an example, you have an api class under both GetMatchHistory and GetMatchDetails.
File: Classes/Queries/GetMatchHistory/api.php
namespace Classes\Queries\GetMatchHistory;
class api
{
public function __construct(){
echo 'this is the GetMatchHistory api';
}
}
File: Classes/Queries/GetMatchDetails/api.php
namespace Classes\Queries\GetMatchDetails;
class api
{
public function __construct(){
echo 'this is the GetMatchDetails api, I am separate to the other!';
}
}
File: Test.php (usage example)
spl_autoload_register('autoload');
$historyApi = new Classes\Queries\GetMatchHistory\api();
$detailsApi = new Classes\Queries\GetMatchDetails\api();
If you like, you can give an alias instead of typing out the whole fully qualified namespace:
use Classes\Queries\GetMatchHistory\api as HistoryApi;
use Classes\Queries\GetMatchDetails\api as DetailsApi;
$historyApi = new HistoryApi();
$detailsApi = new DetailsApi();
As you can see, namespaces make it possible to have multiple different classes with the same name, without having conflicts or making it ambiguous.