I am writing a simple set of PHP functions, I use only pure PHP7, no framework, nothing. These functions will later on be used in a plugin in a CMS, but that is besides the point. I want to write unit tests for my functions using Codeception (to get familiar with it, I know that Codeception essentially only runs PHPUnit here), but I don't really know how to point Codeception to my code in a reasonable way.
My structure is as follows: I have path/to/functions.php which contains the functions I want to test, something along the lines of:
<?php
namespace App;
if (!defined('CONST')) {
die('What are you doing? Get out of here!');
}
function change_string($string){
return $string . '123';
}
I have used Composer to install Codeception to the root of my project and I used Codeception bootstrap to get started and then I also used Codeception to generate the unit test file, to which I added my unit test. Now most tutorials/explanations/articles on the subject just write tests and Codeception magically knowns where to find the code to test. This makes zero sense to me, and does not work in my case. So what I did is the following:
<?php
class NamesPathsTest extends \Codeception\Test\Unit
{
/**
* #var \UnitTester
*/
protected $tester;
protected function _before()
{
defined('CONST') or define('CONST', 'XXX');
require_once('path/to/functions.php');
}
protected function _after()
{
}
// tests
public function testChangeString() {
$this->assertEquals('a123',App\change_string('a'));
}
}
This works, but I think that there must be a better way to explain to Codeception where is the code to run than using the require_once('path/to/functions.php'). How to do this? What is the smart way of pointing Codeception to my code? Can it also handle defining the constant, so that I can actually test the functions?
How does your application code knows where the functions and classes are?
The magic ingredient is called autoloading.
Since you are using Composer already, the easiest way is to configure Composer to load your classes and functions.
Autoloading only works with classes, for them you can map namespace prefix to directory,
{
"autoload": {
"psr-4": {
"Monolog\\": "src/",
"Vendor\\Namespace\\": ""
}
}
}
Files containing functions must be included, but Composer helps with that too:
{
"autoload": {
"files": ["path/to/functions.php"]
}
}
Combined result:
{
"autoload": {
"files": ["path/to/functions.php"]
"psr-4": {
"Monolog\\": "src/",
"Vendor\\Namespace\\": ""
}
}
}
Since Codeception is installed using Composer, no additional work is required to get autoloading work in tests.
To benefit from autoloading in your application code, you must require 'vendor/autoload.php'; near the entry point.
Regarding your constant question, nobody uses this way to prevent direct execution of files, it is much simpler and more secure to move the code away from public directory and leave only small index.php file that can be accessed directly in public directory.
Related
I am upgrading tests from Codeception v2 to v4. The bootstrapping code is referenced in acceptance.suite.yml and loaded just fine.
Visible inside the _bootstrap.php file as of now there was a variable $settings, set by the surrounding Codeception code, that held information about all the live data, that was configured for the tests to run.
This variable is now gone. Printing get_defined_vars() only shows two variables set, strings that point to the current path and bootstrap file name.
How can I access the settings in bootstrapping code again?
I’ve looked at packagist, if there’d be a candidate for a split-off module, that would be of use here, but no candidate looked promising.
Edit: I’ve tried accessing the settings manually:
$settings = \Codeception\Configuration::suiteSettings('acceptance',
\Codeception\Configuration::config());
However, this only allows me access to the “static” settings, i.e., basically as written in the according YAML files. What I need are the “final” settings, i.e., the ones after the environment is evaluated.
I’ve solved this problem by switching from a bootstrap file to an extension. Example:
In codeception.yml:
extensions:
enabled:
- Bootstrapper
The class is found in lib/Bootstrapper.php via Composer’s class list feature, composer.json:
{
"autoload": {
"classmap": [
"lib/"
]
}
}
and looks something like this:
<?php
use Codeception\Events;
use Codeception\Extension;
class Bootstrapper extends Extension {
public static $events = [
Events::SUITE_BEFORE => 'beforeSuite',
];
public function beforeSuite() {
$module = 'PhpBrowser';
if ($this->hasModule('WebDriver')) {
$module = 'WebDriver';
}
/* expose info, if we're in real-browser context */
define('IS_REAL_BROWSER', $module === 'WebDriver');
/* make sure the helper functions are loaded */
require_once __DIR__.'/../tests/acceptance/_helpers.php';
}
}
I have a php project.
This is the composer.json
{
"require": {
"bcosca/fatfree": "3.6.4",
"sineverba/domoticz-api": "^1.0",
"sineverba/supportdate":"dev-v1.0.0-alpha"
},
"repositories": [
{
"type": "vcs",
"url": "git#github.com:sineverba/supportdate.git"
}
]
}
sineverba\domotic-api is published on packagist. sineverba\supportdate no (started tonight).
In a file Script.php under app/models folder
app
| models
Script.php
I require my 2 libraries (domoticz-api and supportdate)
I have
<?php
/**
* Main script class
*
* #since 1.0.0
*/
namespace models;
use \sineverba\domoticzapi as Api;
use \sineverba\supportdate as Supportdate;
class Script() {
//some property here
public function __construct() {
$api = new Api\Client( $this->_user,$this->_password,$this->_host );
$date = new Supportdate\SupportDate();
}
}
$api object is created correctly. $date I get the error Class 'sineverba\supportdate\SupportDate' not found
I did try also:
Remove the declaration use \sineverba\supportdate as Supportdate;
Call directly $date = new \sineverba\supportdate\SupportDate()
As said, $api (object of sineverba\domoticz-api, that is published on packagist) is instantiated correctly, so autoload mode of Fat Free Framework (the framework that I use) is working correctly.
You can also see that domoticz-api and supportdate are very similar also in composer.json.
In my webserver the folders for both libraries are correctly present under /vendor/sineverba/domoticz-api and /vendor/sineverba/supportdate
I did try also creating an index.php inside my supportdate library
<?php
require("vendor/autoload");
$date = new \sineverba\supportdate\SupportDate();
And it works.
So, we can exclude a library / composer error and a F3 framework error ('cause API is called and instantiated).
Thank you for your support
Sometimes I inadvertently add unprintable characters when typing fast. The symptoms you're experiencing fit:
one, but not another similar class works, which rules out composer dump-autoload
it works when typed in another file.
Deleting the line, then retyping slowly forces your mind to dump prior assumptions and start from scratch. This can be helpful if there are issues of unprintable characters, case differences, or any other oddities that sometimes we're blind to in the midst of development.
Hello everyone I'm making mvc framework and want to use Twig template system installed via commposer. I used this tutorial for
MVC here is my composer.json file
{
"require": {
"twig/twig": "~1.0"
},
"autoload":{
"files":[
"config/config.php"
]
}
}
when I add require_once ROOT.'\vendor\autoload.php' to index.php or bootstrap.php I've got an error:
File 'items.php' containing class 'ItemsController' might be missing. 2. Method 'index' is missing in 'items.php'
But when I add require_once in library/View.php like this:
class View {
public $loader;
public $twig;
public $arg;
public function __construct() {
require_once (ROOT.'/vendor/autoload.php');
$loader = new Twig_Loader_Filesystem(ROOT.'/application/views');
$this->twig = new Twig_Environment($loader);
}
}
It works well where is the problem how to make it works when I add autoload outside View class? All suggestions will be helpful, thanks.
That tutorial is using the __autoload() function - this makes it incompatible with everything that comes with it's own autoloader, including Composer.
This restriction is due to PHP being unable to allow defining a function twice - so there can be only one autoload function (which was introduced in PHP 5.0). To solve the problem of wanting to add more than one autoloader, everyone starting with PHP 5.1.2 was asked NOT to define function __autoload(), but to register a function callback using spl_autoload_register().
PHP 5.1.2 was released in January 2006!
Now think about the quality of an MVC tutorial that doesn't even get autoloading right in 2015 (it got posted in 2013, which doesn't make it better).
In fact, Composer can help you here. You can use it to autoload your own classes as well, but it is easiest if you stick to PSR-4 if you use namespaces, or PSR-0 if you don't (I suggest you do, but that Tutorial does not - another low quality, I'd say).
I have an interface which I named Manager, and two classes UtilisateurManager and StageManager, and the both of them implements the Manager interface.
In each class I included the interface Manager as the following :
require '../helpers/Manager.class.php';
Then I needed to use these two classes at once in a php script, but I got the error that I can't redeclare class Manager.
I tried to work with the function class_exists() but it's not useful in my case.
How can I solve this problem ?
Maybe you can use require_once instead of require.
require_once '../helpers/Manager.class.php';
You need to implement a simple autoloader which will try to load class/interface only if it isn't already loaded, and forget about direct require/include then. It is considered good practice to leave the filesystem details out of the class file: you may reuse it in other project where your directories system might change, so you'll need to edit the class again.
A simple example:
class Autoloader
{
protected $directories = array();
public function register()
{
// register 'loadClass' method as an autoloader
spl_autoload_register(array($this, 'loadClass'));
}
public function addSource($dir)
{
$this->directories[] = $dir;
}
public function loadClass($className)
{
foreach ($this->directories as $dir) { // search in every registered sources root
$fileName = $dir.DIRECTORY_SEPARATOR.$className.'.class.php';
if (file_exists($fileName) {
include($fileName);
return; // halt when file was found
}
}
}
}
// bootstrap file
$autoloader = new Autoloader;
$autoloader->register();
(spl_autoload_register documentation)
Please note that this realization will cycle through the folders, and this is certainly not the desired solution. The best option today is to build project according to PSR-4 spec - autoloader implementation will have to check existence of a single file only. It may be a little hard to dive into namespaces without any preparation, but once you get familiar with this coding style, you will forget about direct file load.
The last thing, i want to warn you about your naming convention - your Interface is stored in a .class.php file, and this is confusing.
I'm trying to use PHP namespaces for the first time and can't even get a very basic example working with 2 files. Here's my directory setup:
/Framework/
/Framework/index.php
/Framework/Models/TestModel.php
And here's the code behind the two files.
index.php:
namespace Framework;
use \Framework\Models\TestModel;
$model = new TestModel();
$model->test();
TestModel.php:
namespace Framework\Models;
class TestModel
{
public function test()
{
print("test");
}
}
The error is simply that it cannot find the TestModel class:
Fatal error: Class 'Framework\Models\TestModel' not found in C:\xampp\htdocs\Framework\index.php on line 7
I'm running the PHP via a web browser at localhost/Framework/index.php. It must be something really simple I'm not seeing, can anyone point it out for me?
The Namespace on the File itself "distinguishes" from other classes and functions, however PHP/Server does not know where the physical file is simply based on a Namespace.
So including the file directly, as people has mentioned, lets PHP know exactly what you mean.
When PHP can't find the file, it will call the function spl_autoload_register() and in that method people will usually put a little function to match namespace to directory structure and then load files according.
Another option is to include Composer in your project and use the PSR-4 autoload
{
"require": {
},
"autoload": {
"psr-4": {
"App\\": "app_directoy/",
"Framework\\": "framework_directory/",
}
}
}
When including the composer autoload it will look for everything Framework/* within your framework_directory as you defined.
You should remove 'namespace Framework' and include TestModel.php instead in your index.php - Something like this:
require_once('Models/TestModel.php');
use \Framework\Models\TestModel;
$model = new TestModel();
$model->test();