Question in short
How can I make the Autoloader find all classes required to run my PHP tests?
Question in detail
I want to autoload the classes that I am using in PHPUnit in Eclipse. My directory structure is as follows.
Project (called yii-app)
protected
dirA
classA.php
dirB
classB.php
yii-1.1.14.f0fee9
Yii.php
tests
ClassATest.php
ClassBTest.php
bootstrap.php
Autoloader.php
I use the bootstrap.php and Autoloader.php that I found here, see below for details. The Class classA does not make use of the Yii framework, and the tests in ClassATest run smoothly. The Class classB does make use of the Yii framework. One of the first lines is:
Yii::import('application.<path into some directory>.*')
When I try to run the tests in ClassBTest.php, I get the following error.
Fatal error: Class 'Yii' not found in /Users/physicalattraction/git/yii-app/protected/dirB/classB.php on line 3
Even if I register the entire project directory (including subdirectories), Class Yii is not found, while it is right there. What should I change to make these tests run as well?
Note
I have the same problem if I try to run the tests directly from the terminal, so it is not Eclipse related.
$ ./composer/vendor/bin/phpunit --bootstrap=tests/bootstrap.php tests
PHPUnit 4.5.1 by Sebastian Bergmann and contributors.
Fatal error: Class 'Yii' not found in /Users/physicalattraction/git/yii-app/protected/dirB/classB.php on line 3
Details
PHPUnit settings in Eclipse
bootstrap.php
<?php
include_once('AutoLoader.php');
// Register the directory to your include files
Toolbox\Testing\AutoLoader::registerDirectory(__DIR__.'/../yii-1.1.14.f0fee9');
Toolbox\Testing\AutoLoader::registerDirectory(__DIR__.'/../protected');
?>
Autoloader.php
<?php
namespace Toolbox\Testing;
/**
* This class is an auto loader for use with vanilla PHP projects' testing environment. Use it in
* the bootstrap to register classes without having to use a framework (which you can, and should if
* it's a better solution for you) and without having to use includes everywhere.
*
* It assumes that the file path in relation to the namespace follows the PSR-0 standard.
*
* IMPORTANT NOTE: When just registering directories, the class has no ability to discern
* conflicting class names in different namespaces, which means that classes with the same name will
* override each other! Always use the registerNamespace()-method if possible!
*
* Inspired by Jess Telford's AutoLoader (http://jes.st/).
*
* #see http://jes.st/2011/phpunit-bootstrap-and-autoloading-classes/
* #see http://petermoulding.com/php/psr
* #see http://www.php-fig.org/psr/psr-0/
*
* #codeCoverageIgnore
*
* #category Toolbox
* #package Testing
*
* #author Helge Söderström <helge.soderstrom#schibsted.se>
*/
class AutoLoader {
/**
* An array keeping class names as key and their path as the value for classes registered with
* AutoLoader::registerNamespace().
*
* #var array
*/
protected static $namespaceClassNames = array();
/**
* An array keeping class names as key and their path as the value for classes registered with
* AutoLoader::registerDirectory().
*
* #var array
*/
protected static $directoryClassNames = array();
/**
* Store the filename (sans extension) & full path to all ".php" files found for a namespace.
* The parameter should contain the root namespace as the key and the directory as a value.
*
* #param string $namespace
* #param string $dirName
* #return void
*/
public static function registerNamespace($namespace, $dirName) {
$directoryContents = new \DirectoryIterator($dirName);
foreach($directoryContents as $file) {
if ($file->isDir() && !$file->isLink() && !$file->isDot()) {
$newNamespace = $namespace . "_" . $file->getFileName();
$newDirName = $dirName . "/" . $file->getFilename();
static::registerNamespace($newNamespace, $newDirName);
} elseif (substr($file->getFilename(), -4) === '.php') {
$className = substr($file->getFilename(), 0, -4);
$namespacedClassName = $namespace . "_" . $className;
$fileName = realpath($dirName) . "/" . $file->getFilename();
static::$namespaceClassNames[$namespacedClassName] = $fileName;
}
}
}
/**
* Store the filename (sans extension) & full path of all ".php" files found.
*
* NOTE: This method will not be able to differentiate the same class names in different
* namespaces and will therefore overwrite class names if multiple of the same name is
* found. If possible, use registerNamespace instead!
*
* #param string $dirName
* #return void
*/
public static function registerDirectory($dirName) {
$directoryContents = new \DirectoryIterator($dirName);
foreach ($directoryContents as $file) {
if ($file->isDir() && !$file->isLink() && !$file->isDot()) {
// Recurse into directories other than a few special ones.
static::registerDirectory($file->getPathname());
} elseif (substr($file->getFilename(), -4) === '.php') {
// Save the class name / path of a .php file found.
$className = substr($file->getFilename(), 0, -4);
AutoLoader::registerClass($className, $file->getPathname());
}
}
}
/**
* Caches a found class with the class name as key and its path as value for use when loading
* on the fly. The class is registered with its class name only, no namespace.
*
* #param string $className
* #param string $fileName
* #return void
*/
public static function registerClass($className, $fileName) {
AutoLoader::$directoryClassNames[$className] = $fileName;
}
/**
* Includes a found class in the runtime environment. Strips namespaces.
*
* #param string $className
* #return void
*/
public static function loadClass($className) {
// First, see if we've registered the entire namespace.
$namespacedClassName = str_replace('\\', '_', $className);
if (isset(static::$namespaceClassNames[$namespacedClassName])) {
require_once(static::$namespaceClassNames[$namespacedClassName]);
return;
}
// Nope. Have we registered it as a directory?
$psrDirectorySeparators = array('\\', '_');
foreach($psrDirectorySeparators as $separator) {
$separatorOccurrence = strrpos($className, $separator);
if($separatorOccurrence !== false) {
$className = substr($className, $separatorOccurrence + 1);
break;
}
}
if (isset(AutoLoader::$directoryClassNames[$className])) {
require_once(AutoLoader::$directoryClassNames[$className]);
}
}
}
// Register our AutoLoad class as the system auto loader.
spl_autoload_register(array('Toolbox\Testing\AutoLoader', 'loadClass'));
?>
The Autoloader probably doesn't find the YII class. Did you try adding:
Toolbox\Testing\AutoLoader::registerDirectory(DIR.'/../yii-1.1.14.f0fee9/framework');
to your bootstrap.php file. I think that the YII class is defined in the framework directory.
Another thing which you can try is to use the composer autoloader instead.
P.S
It is a good practice to mirror your app directory/file structure in the tests directory. In your case ClassATest.php and ClassBTest.php should be separated into their own directories the same way they are separated in the protected directory.
I found that with the following changes, it worked.
Have the following directory structure
project
protected
config
main.php
test.php
controllers
models
tests
fixtures
functional
report
unit
bootstrap.php
phpunit.xml
In main.php: add the directories in which Yii looks for classes. Subdirectories are not automatically searched for by Yii, so you have to specify each directory individually here.
// autoloading model and component classes
'import' => array(
'application.models.*',
'application.models.support.*',
'application.components.*',
....
Define the following test configuration in test.php.
<?php
return CMap::mergeArray(
require(dirname(__FILE__).'/main.php'),
array(
'components'=>array(
'fixture'=>array(
'class'=>'system.test.CDbFixtureManager',
),
'db'=>array(
'class' => 'CDbConnection',
'connectionString' => 'CONNECTIONSTRING',
'emulatePrepare' => true,
'username' => 'USERNAME',
'password' => 'PASSWORD',
'charset' => 'utf8',
),
),
)
);
?>
Then, in the bootstrap file, only use the Yii Autoloader.
<?php
$yiit=__DIR__.'/../../yii-1.1.14.f0fee9/yiit.php';
$config=dirname(__FILE__).'/../config/test.php';
require_once($yiit);
// Include the following line if you want to write a WebTestCase
// require_once(dirname(__FILE__).'/WebTestCase.php');
Yii::createWebApplication($config);
?>
My test case needs a model of type Order, which is connected to a database table orders. Define a fixture in directory fixtures/Order.php.
<?php
return array(
'order1' => array(
'id' => 1011,
'order_number' => 'on_101'
)
);
?>
Make the test case as indicated on the Yii and PHPUnit websites.
class MyExportTest extends CDbTestCase
{
public $fixtures = array(
'orders' => 'Order'
);
public function test_order()
{
$order = $this->orders('order1');
$this->assertTrue($order instanceof Order);
$r = $order->order_number;
$e = "on_101";
$this->assertEquals($r, $e,
sprintf("Received order_number: \n%s\nExpected order_number: \n%s\n", $r, $e));
}
public function test_export()
{
$sut = new MyExport($tenantId, $userGroupId);
$r = $sut->method_to_test()
$e = "expected result";
$this->assertEquals($r, $e,
sprintf("Received result: \n%s\nExpected result: \n%s\n", $r, $e));
}
}
Related
I have som legacy code that broke after an update of a dependency class of my ex colleague's Handler class of the Cosenary Instagram Class using composer.
The former reference to include the class in the handler class was this:
namespace Acme\Instagram;
class Handler
{
/* GOT SOME USEFUL VARS HERE */
const API_URL = 'https://api.instagram.com/v1';
/**
* Initialize the library
*
* #param array $settings Contains our ClientId and upload dir
* #param string $root_dir The root of our application
*/
public function __construct( $settings, $root_dir = __DIR__ )
{
include_once $root_dir . '/vendor/cosenary/instagram/instagram.class.php'; //HERE WAS THE INCLUDE BEFORE
// THROW SOME EXCEPTIONS HERE IF SETTINGS MISSING ETC...
// New instance of the instagram class
$this->_instagram = new \Instagram($this->_clientId);
}
/* HANDLE SOM FUNCTIONS HERE */
}
And if I change the include_once to:
require_once $root_dir . '/vendor/cosenary/instagram/src/Instagram.php';
Then I get:
Fatal error: Class 'Acme\Instagram\Instagram' not found in
I guess I need to pass it in as a reference in the constructor but that means I need to rewrite a lot of code and there is probably 5-10 other projects that is depending on this Hanlder class. Is there a way to use the instagram class in this other class?
Tried moving out the include_once and:
use MetzWeb\Instagram\Instagram;
But no luck, any help or pointers i greatly appreciated.
I'm not sure how is Your app's structure looks.
But try this:
namespace Acme\Instagram;
require_once $root_dir.'/vendor/autoload.php';
use MetzWeb\Instagram\Instagram AS InstagramClient;
class Handler
{
/* GOT SOME USEFUL VARS HERE */
const API_URL = 'https://api.instagram.com/v1';
/**
* Initialize the library
*
* #param array $settings Contains our ClientId and upload dir
* #param string $root_dir The root of our application
*/
public function __construct( $settings, $root_dir = __DIR__ )
{
// New instance of the instagram class
$this->_instagram = new InstagramClient($this->_clientId);
}
/* HANDLE SOM FUNCTIONS HERE */
I want to use https://github.com/box/spout library in my Yii project (in one of commands). I'm not using Composer, so I simple downloaded the extension and put in extensions/spout/Box/.
In my config/main.php I've added line
'import' => array(
...
'application.extensions.spout.*'
),
And in my command I've added following lines:
require_once Yii::app()->basePath . '/extensions/spout/Box/Spout/Reader/ReaderFactory.php';
require_once Yii::app()->basePath . '/extensions/spout/Box/Spout/Common/Type.php';
When I'm calling $reader = ReaderFactory::create(Type::CSV); I'm getting following error:
PHP Error[2]: include(ReaderFactory.php): failed to open stream: No such file or directory
in file /srv/yii/YiiBase.php at line 421
#0 /srv/yii/YiiBase.php(421): autoload()
#1 unknown(0): autoload()
#2 /srv/dev/protected/commands/AmazonCommand.php(193): spl_autoload_call()
#3 unknown(0): AmazonCommand->actionIndex()
#4 /srv/yii/console/CConsoleCommand.php(172): ReflectionMethod->invokeArgs()
#5 /srv/yii/console/CConsoleCommandRunner.php(67): AmazonCommand->run()
#6 /srv/yii/console/CConsoleApplication.php(91): CConsoleCommandRunner->run()
#7 /srv/yii/base/CApplication.php(169): CConsoleApplication->processRequest()
#8 /srv/yii/yiic.php(33): CConsoleApplication->run()
#9 /srv/dev/protected/yiic.php(19): require_once()
#10 /srv/dev/protected/yiic(4): require_once()
What I'm missing? How can I use third party library in my project?
UPDATE
After that tutorial, I've moved spout folder in protected/vendors folder and changed the code following way:
Yii::import('application.vendors.spout.Box.Spout.Reader.*');
Yii::import('application.vendors.spout.Box.Spout.Common.*');
require_once 'ReaderFactory.php';
require_once 'Type.php';
Now I'm getting following error, still no clue why:
PHP Fatal error: Cannot redeclare class Box\Spout\Reader\ReaderFactory in /srv/dev/protected/vendors/spout/B
ox/Spout/Reader/ReaderFactory.php on line 17
If you are using Yii 2.0 and since Spout is PSR4 compliant, you can follow this guide: http://www.yiiframework.com/doc-2.0/guide-structure-extensions.html#installing-extensions-manually
If you are still using Yii 1.1, I am not sure what the best way to autoload your classes is. But you can still use a standard PSR4 autoloader:
Psr4Autoloader.php
namespace Autoloader;
class Psr4Autoloader
{
/**
* An associative array where the key is a namespace prefix and the value
* is an array of base directories for classes in that namespace.
*
* #var array
*/
protected $prefixes = array();
/**
* Register loader with SPL autoloader stack.
*
* #return void
*/
public function register()
{
spl_autoload_register(array($this, 'loadClass'));
}
/**
* Adds a base directory for a namespace prefix.
*
* #param string $prefix The namespace prefix.
* #param string $base_dir A base directory for class files in the
* namespace.
* #param bool $prepend If true, prepend the base directory to the stack
* instead of appending it; this causes it to be searched first rather
* than last.
* #return void
*/
public function addNamespace($prefix, $base_dir, $prepend = false)
{
// normalize namespace prefix
$prefix = trim($prefix, '\\') . '\\';
// normalize the base directory with a trailing separator
$base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
// initialize the namespace prefix array
if (isset($this->prefixes[$prefix]) === false) {
$this->prefixes[$prefix] = array();
}
// retain the base directory for the namespace prefix
if ($prepend) {
array_unshift($this->prefixes[$prefix], $base_dir);
} else {
array_push($this->prefixes[$prefix], $base_dir);
}
}
/**
* Loads the class file for a given class name.
*
* #param string $class The fully-qualified class name.
* #return mixed The mapped file name on success, or boolean false on
* failure.
*/
public function loadClass($class)
{
// the current namespace prefix
$prefix = $class;
// work backwards through the namespace names of the fully-qualified
// class name to find a mapped file name
while (false !== $pos = strrpos($prefix, '\\')) {
// retain the trailing namespace separator in the prefix
$prefix = substr($class, 0, $pos + 1);
// the rest is the relative class name
$relative_class = substr($class, $pos + 1);
// try to load a mapped file for the prefix and relative class
$mapped_file = $this->loadMappedFile($prefix, $relative_class);
if ($mapped_file) {
return $mapped_file;
}
// remove the trailing namespace separator for the next iteration
// of strrpos()
$prefix = rtrim($prefix, '\\');
}
// never found a mapped file
return false;
}
/**
* Load the mapped file for a namespace prefix and relative class.
*
* #param string $prefix The namespace prefix.
* #param string $relative_class The relative class name.
* #return mixed Boolean false if no mapped file can be loaded, or the
* name of the mapped file that was loaded.
*/
protected function loadMappedFile($prefix, $relative_class)
{
// are there any base directories for this namespace prefix?
if (isset($this->prefixes[$prefix]) === false) {
return false;
}
// look through base directories for this namespace prefix
foreach ($this->prefixes[$prefix] as $base_dir) {
// replace the namespace prefix with the base directory,
// replace namespace separators with directory separators
// in the relative class name, append with .php
$file = $base_dir
. str_replace('\\', '/', $relative_class)
. '.php';
// if the mapped file exists, require it
if ($this->requireFile($file)) {
// yes, we're done
return $file;
}
}
// never found it
return false;
}
/**
* If a file exists, require it from the file system.
*
* #param string $file The file to require.
* #return bool True if the file exists, false if not.
*/
protected function requireFile($file)
{
if (file_exists($file)) {
require $file;
return true;
}
return false;
}
}
Then add this code to your root file or wherever you think it's the most appropriate (just make sure the path for "require_once" is correct):
autoload.php
require_once "Psr4Autoloader.php";
$loader = new \Autoloader\Psr4Autoloader;
$loader->register();
$loader->addNamespace('Box\Spout', 'vendor/box/spout/src/Spout');
You should now be able to use Spout!
I am trying to edit the PHPWord plugin to better fit my needs. By making the list of sections that the PHPWord main class holds, I can make it so I can insert sections into any order in the document instead of just a fixed list that I can't re-order.
But I am getting an error saying syntax error, unexpected 'new' and it is driving me crazy.
Does anybody else know more about this PHPWord tool? I'm not getting answers from their Github issue tracker/forum.
Something along the lines of they have their defined classes that the tools uses and I need to add it somehow as a resource? I can't just add it in for some reason.
Mucho gracias!
EDIT:
I tried making a new object on this line OTHER than array() or SplDoublyLinkedList() and no matter what other object I try to assign that is outside of the plugin scope I get the 'new' syntax error.
I also tried new ArrayObject(); and I'm still getting the error.
PHPWord uses an Autoloader? This is their autoloader code, do I need to somehow include any extra classes I want to use in here somehow?
namespace PhpOffice\PhpWord;
/**
* Autoloader
*/
class Autoloader
{
/** #const string */
const NAMESPACE_PREFIX = 'PhpOffice\\PhpWord\\';
/**
* Register
*
* #param bool $throw
* #param bool $prepend
* #return void
*/
public static function register($throw = true, $prepend = false)
{
spl_autoload_register(array(new self, 'autoload'), $throw, $prepend);
}
/**
* Autoload
*
* #param string $class
* #return void
*/
public static function autoload($class)
{
$prefixLength = strlen(self::NAMESPACE_PREFIX);
if (0 === strncmp(self::NAMESPACE_PREFIX, $class, $prefixLength)) {
$file = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, $prefixLength));
$file = realpath(__DIR__ . (empty($file) ? '' : DIRECTORY_SEPARATOR) . $file . '.php');
if (file_exists($file)) {
/** #noinspection PhpIncludeInspection Dynamic includes */
require_once $file;
}
}
}
}
You have probably gotten already past this problem, but just to write out the problem answer here in case someone stumbles here and tries to make sense of this case.
As #karthikr mentioned, the problem is using object initialization with new when defining class variable (php: properties):
This declaration may include an initialization, but this initialization must be a constant value--that is, it must be able to be evaluated at compile time and must not depend on run-time information in order to be evaluated.
i.e. the correct way to do the initialization is to move the new to the PhpWord class constructor:
private $sections = null;
public function __construct()
{
$sections = new SplDoublyLinkedList();
...
}
If someone work with GO! framework, can you help me.
I install framework on php 5.3.13. Demo example is working.
But my own example doesn't work. Aspect(method beforeMethodExecution) is not perfomed.
Here is my code.
Main file:
//1 Include kernel and all classes
if (file_exists(__DIR__ .'/../../vendor/autoload.php')) {
$loader = include __DIR__ .'/../../vendor/autoload.php';
}
// 2 Make own ascpect kernel
use Go\Core\AspectKernel;
use Go\Core\AspectContainer;
class Kernel extends AspectKernel{
/**
* Configure an AspectContainer with advisors, aspects and pointcuts
*
* #param AspectContainer $container
*
* #return void
*/
public function configureAop(AspectContainer $container)
{
}
}
//3 Initiate aspect kernel
$Kernel = Kernel::getInstance();
$Kernel->init();
//4 Include aspect
include(__DIR__.'/aspectclass/AspectClass.php');
$aspect = new DebugAspect();
//5 register aspect
$Kernel->getContainer()->registerAspect($aspect);
//6 Include test class
include(__DIR__.'/class/class1.php');
//7 Execute test class
$Class = new General('test');
$Class->publicHello();
File with test class:
class General{
protected $message = '';
public function __construct($message)
{
$this->message = $message;
}
public function publicHello()
{
echo 'Hello, you have a public message: ', $this->message, "<br>", PHP_EOL;
}
}
File with aspect:
use Go\Aop\Aspect;
use Go\Aop\Intercept\FieldAccess;
use Go\Aop\Intercept\FunctionInvocation;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\After;
use Go\Lang\Annotation\Before;
use Go\Lang\Annotation\Around;
use Go\Lang\Annotation\Pointcut;
use Go\Lang\Annotation\DeclareParents;
use Go\Lang\Annotation\DeclareError;
class DebugAspect implements Aspect{
/**
* Method that should be called before real method
*
* #param MethodInvocation $invocation Invocation
* #Before("execution(General->*(*))")
*
*/
public function beforeMethodExecution(MethodInvocation $invocation)
{
$obj = $invocation->getThis();
echo 'Calling Before Interceptor for method: ',
is_object($obj) ? get_class($obj) : $obj,
$invocation->getMethod()->isStatic() ? '::' : '->',
$invocation->getMethod()->getName(),
'()',
' with arguments: ',
json_encode($invocation->getArguments()),
PHP_EOL;
}
}
As you know, go-aop isn't a PHP extension, so it couldn't transform classes that were loaded directly via require or include. Internally it tries to overwrite the source code on-the-fly, but it should receive a control (via integration with composer or custom autoloader class).
So, you have an error here:
//6 Include test class
include(__DIR__.'/class/class1.php');
You explicitly load this class into memory and there is no way to transform it from userland. To pass a control to the framework, you should make this explicitly. Look at the line AopComposerLoader.php#L99 to have an idea how it works. Here we include a source file via the stream source filter that pass control to the framework and it can transform the class to weave an aspects.
To fix your example just change an include to the following:
include (FilterInjectorTransformer::rewrite(__DIR__.'/class/class1.php'));
Seems like this is a very common problem for beginners with CodeIgniter, but none of the solutions I've found so far seems very relevant to my problem. Like the topic says I'm trying to include a custom class in CodeIgniter.
I'm trying to create several objects of the class below and place them in an array, thus I need the class to be available to the model.
I've tried using the load (library->load('myclass') functions within CodeIgniter which sort of works, except it tries to create an object of the class outside the model first. This is obviously a problem since the constructor expects several parameters.
The solutions I've found so far is
A simple php include which seems fine enough, but since I'm new to
CodeIgniter I want to make sure I'm sticking to it as much as
possible.
Creating a "wrapper class" as suggested here, however I'm uncertain how I would implement this.
The class I want to include,
User.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class User{
public $ID = 0;
public $username = 0;
public $access_lvl = 0;
public $staff_type = 0;
public $name = 0;
public function __construct($ID, $username, $access_lvl, $staff_type, $name)
{
$this->ID = $ID;
$this->username = $username;
$this->access_lvl = $access_lvl;
$this->staff_type = $staff_type;
$this->name = $name;
}
public function __toString()
{
return $this->username;
}
}
?>
Method (Model) which needs the User.php
function get_all_users()
{
$query = $this->db->get('tt_login');
$arr = array();
foreach ($query->result_array() as $row)
{
$arr[] = new User
(
$row['login_ID'],
$row['login_user'],
$row['login_super'],
$row['crew_type'],
$row['login_name']
);
}
return $arr;
}
And finally the controller,
function index()
{
$this->load->library('user');
$this->load->model('admin/usersmodel', '', true);
// Page title
$data['title'] = "Some title";
// Heading
$data['heading'] = "Some heading";
// Data (users)
$data['users'] = $this->usersmodel->get_all_users();
If you have PHP version >= 5.3 you could take use of namespaces and autoloading features.
A simple autoloader library in the library folder.
<?php
class CustomAutoloader{
public function __construct(){
spl_autoload_register(array($this, 'loader'));
}
public function loader($className){
if (substr($className, 0, 6) == 'models')
require APPPATH . str_replace('\\', DIRECTORY_SEPARATOR, $className) . '.php';
}
}
?>
The User object in the model dir. ( models/User.php )
<?php
namespace models; // set namespace
if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class User{
...
}
And instead of new User... new models\User ( ... )
function get_all_users(){
....
$arr[] = new models\User(
$row['login_ID'],
$row['login_user'],
$row['login_super'],
$row['crew_type'],
$row['login_name']
);
...
}
And in controller just make sure to call the customautoloader like this:
function index()
{
$this->load->library('customautoloader');
$this->load->model('admin/usersmodel', '', true);
// Page title
$data['title'] = "Some title";
// Heading
$data['heading'] = "Some heading";
// Data (users)
$data['users'] = $this->usersmodel->get_all_users();
CodeIgniter doesn't really support real Objects.
All the libraries, models and such, are like Singletons.
There are 2 ways to go, without changing the CodeIgniter structure.
Just include the file which contains the class, and generate it.
Use the load->library or load_class() method, and just create new objects. The downside of this, is that it will always generate 1 extra object, that you just don't need. But eventually the load methods will also include the file.
Another possibility, which will require some extra work, is to make a User_Factory library.
You can then just add the object on the bottom of the file, and create new instances of it from the factory.
I'm a big fan of the Factory pattern myself, but it's a decision you have to make yourself.
I hope this helped you, if you have any questions that are more related to the implementation, just let me/us know.
Including a class file is not a bad approach.
In our projects, we do the same, add an another layer to MVC, and thats a Service Layer which the Controllers calls and Service calls the Model. We introduced this layer to add Business Logic seperate.
So far, we have been using it, and our product has gone large too, and still we find no difficulty with the decision of including files that we had made in the past.
Codeigniter has a common function to instantiate individual classes.
It is called load_class(), found in /system/core/Common.php
The function;
/**
* Class registry
*
* This function acts as a singleton. If the requested class does not
* exist it is instantiated and set to a static variable. If it has
* previously been instantiated the variable is returned.
*
* #access public
* #param string the class name being requested
* #param string the directory where the class should be found
* #param string the class name prefix
* #return object
*/
The signature is
load_class($class, $directory = 'libraries', $prefix = 'CI_')
An example of it being used is when you call the show_404() function.
After a brief google search, I was inspired to make my own autoloader class. It's a bit of a hack, since I use custom Codeigniter library to preform auto-loading, but for me this is the best way, that I'm aware of, of loading all the classes, I require, without compromising my application architecture philosophy, to fit it into Codeigniter way of doing things. Some might argue that Codeigniter is not the right framework for me and that might be true, but I'm trying things out and playing around with various frameworks and while working on CI, I came up with this solution.
1. Auto-load new custom library by editing applicaion/config/autoload.php to include:
$autoload['libraries'] = array('my_loader');
and any other libraries you might need.
2. Then add library class My_loader. This class will be loaded on every request and when its constructor is run, it will recursively search through all sub-folders and require_once all .php files inside application/service & application/models/dto folders. Warning: folders should not have dot in the name, otherwise function will fail
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class My_loader {
protected static $_packages = array(
'service',
'models/dto'
);
/**
* Constructor loads service & dto classes
*
* #return void
*/
public function __construct($packages = array('service', 'models/dto'))
{
// files to be required
$toBeRequired = array();
// itrate through packages
foreach ($packages as $package) {
$path = realpath(APPPATH . '/' . $package . '/');
$toBeRequired = array_merge($toBeRequired, $this->findAllPhpFiles($path));
}
/**
* Require all files
*/
foreach ($toBeRequired as $class) {
require_once $class;
}
}
/**
* Find all files in the folder
*
* #param string $package
* #return string[]
*/
public function findAllPhpFiles($path)
{
$filesArray = array();
// find everithing in the folder
$all = scandir($path);
// get all the folders
$folders = array_filter($all, get_called_class() . '::_folderFilter');
// get all the files
$files = array_filter($all, get_called_class() . '::_limitationFilter');
// assemble paths to the files
foreach ($files as $file) {
$filesArray[] = $path . '/' . $file;
}
// recursively go through all the sub-folders
foreach ($folders as $folder) {
$filesArray = array_merge($filesArray, $this->findAllPhpFiles($path . '/' . $folder));
}
return $filesArray;
}
/**
* Callback function used to filter out array members containing unwanted text
*
* #param string $string
* #return boolean
*/
protected static function _folderFilter($member) {
$unwantedString = '.';
return strpos($member, $unwantedString) === false;
}
/**
* Callback function used to filter out array members not containing wanted text
*
* #param string $string
* #return boolean
*/
protected static function _limitationFilter($member) {
$wantedString = '.php';
return strpos($member, $wantedString) !== false;
}
}
After 18 hours I managed to include a library in my control without initialisation (the constructor was the problem, because of that and i could't use the standard codeiginiter $this->load->library() ).
Follow the https://stackoverflow.com/a/21858556/4701133 . Be aware for further native class initialization use $date = new \DateTime()with back-slash in front otherwise the function will generate an error !