Initializing PHP classes in the Web server/browser context - php

I am writing a script that talks to a database, and transforms the content and puts it into an XML file.
I am trying to utilize best practices, and as part of library re-use, I have found the following classes to be beneficial:
Symfony components:
Yaml : for parsing and loading database info.,
DI Container : for injecting dependencies
Others:
PHPExcel, the amazing excel library
Doctrine DBAL to get an easy PDO wrapper
Mockery for obtaining easy mocks during testing
My core business class, ExcelExporter, will need all these files functioning harmoniously with each other. I also need a place where I can initialize by own class, and inject these dependencies in.
I am confused as to where to do this. If I were building a web page, I'd say I'd put the initialization & configuration process inside index.php and finally call ExcelExporter to do its thing when a user loads index.php in the browser. Is there a better way for this bootstrapping process? What's the most common approach?

You should totaly use Composer for this task.
It will handle vendors and autoloading for you.
Here is a quick setup:
curl -s https://getcomposer.org/installer | php
php composer.phar init
php composer.phar require symfony/dom-crawler:dev-master doctrine/dbal:2.4.x-dev
php composer.phar install
You will have a vendor directory with your dependencies. Then the bootstrap file is simply initializing what you have to do. Autoloading is handled by Composer:
// Then add this on top of your bootstrap file
require 'vendor/autoload.php';

I assume you are not using any framework. Only using symphony components. If you were using a framework it would have been done by the framework. But for vanila framework you need
A single entry point for the application (say index.php)
A bootstrap.php file that loads initial classes and external resources. Many framework uses this bootstrap files. Just look at the file structure you'll find them. The name might not be bootstrap.php.
Once your application is loaded use a autoloader to load on-demand classes. This autoloader should be initialized on bootstrap.
function my_autoloader($class) {
include 'classes/' . $class . '.class.php';
}
spl_autoload_register('my_autoloader');

Based on Damien's answer, I found that Composer offers the easiest method to do this.
Now, my ExportExcel class lives in the src/ directory. The structure of my directory is:
src/
vendor/
index.php
bootstrap.php
composer.json
Within composer.json, I also asked it to load my classes for me:
{
"name": "adityamenon/excel_export",
"description": "Export data into a custom excel sheet format",
"require": {
"symfony/dependency-injection": "2.2.x-dev",
"symfony/yaml": "2.1.x-dev",
"doctrine/dbal": "2.4.x-dev",
"mockery/mockery": "dev-master",
"CodePlex/PHPExcel": "1.7.8"
},
"autoload": {
"psr-0": {"ExportExcel": "src/"}
},
"authors": [
{
"name": "aditya menon",
"email": "adityamenon90#gmail.com"
}
]
}
I just need to run $ composer install for all the dependencies to appear magically within vendor/.
PSR-0 is easy to follow. I just made a file called ExportExcel.php with a namespaced class inside called ExportExcel:
namespace ExportExcel;
class ExportExcel {
public function __construct(\ExportExcel\DbConvenience $db_convenience)
{
$this->DbConvenience = $db_convenience;
}
public function writeExcelFile()
{
// ...do stuff here
}
}
Finally, here's bootstrap.php, which takes care of autoloading stuff I need as well as injecting dependencies:
<?php
// composer autoloads dependencies
// also configured to load from src/
$loader = require('vendor/autoload.php');
// load configuration (only db right now)
use Symfony\Component\Yaml\Yaml;
$yaml_config = Yaml::parse('config.yml');
// load the database
$db_config = new Doctrine\DBAL\Configuration();
$conn = Doctrine\DBAL\DriverManager::getConnection($yaml_config, $db_config);
// load the convenience classes
$DbConvenience = new ExportExcel\DbConvenience($conn);
// use the dependency injector to load the exporter with DB
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
$sc = new ContainerBuilder();
$sc->setParameter('dbconvenience.class', 'ExportExcel\DbConvenience');
$sc->setParameter('exportexcel.class', 'ExportExcel\ExportExcel');
$sc
->register('dbconvenience', '%dbconvenience.class%')
->addArgument($conn);
$sc
->register('exportexcel', '%exportexcel.class%')
->addArgument(new Reference('dbconvenience'));
// here's the most important class of 'em all
$ExportExcel = $sc->get('exportexcel');
Finally in index.php, the first line is require('bootstrap.php');, and off I go. I can make calls to functions inside ExportExcel with $ExportExcel->doStuff();
Please feel free to add comments and shorten/improve this answer. Solving this puzzle was a bit hard and took a bit of reading around but everything is so clear in hindsight.

Related

Autoloading PHP file in composer package

I'm trying to create a composer package that also contains src/functions.php with some general functions. I have the following in composer.json to make it autoload:
"autoload": {
"files": ["src/functions.php"]
}
When I import this package into a project it will try to load src/functions.php in the current project (local) in stead of the imported package. Is there a way to ensure the correct file is loaded when imported (./vendor/bla/src/functions.php)?
Autoloading is not for loading everything. If src/functions.php contains class just ensure it's properly namespaced and I see no reason why autoloader would pick your local class instead of package's. If you are using the same namespace for the package and for code in your project then basically you should stop doing so.
If src/functions.php is just bunch of functions, then I strognly suggest refactoring the code and wrap them in properly namespaced class. You can make your functions static methods so basically not much would change from usage perspective.
EDIT
Once you finish refactoring, change your composer.json from what you shown in question to:
"autoload": {
"classmap": ["src/"]
}

Fatal error: Class 'CDbTestCase' not found

I'm new in php and I have to do the testing for an app. I'm trying to make unit testing but an error messages is displayed. I have many weeks ago and I cannot fix it, please help me!!
The message says: Fatal error: Class 'CDbTestCase' not found.
I read and follow many tutorials about this issue but it doesn't work.
I'm using Yii, Eclipse IDE and Composer.
I think the problem is in bootstrap.php but I don't use it because I'm working with composer, this is the composer.json
{
"require-dev": {
"yiisoft/yii": "1.1.*",
"phpunit/phpunit": "4.6.*",
"phpunit/phpunit-selenium": ">=1.2",
"codeception/codeception":"*",
"phpunit/dbunit": ">=1.2"
},
"autoload": {
"psr-0": {"": "vendor/autoload.php"},
"psr-4": {"": "/../framework/test/CDbTestCase.php"}
}
}
The bootstrap file is needed to load the yii framework. CDbTestCase is part of the yii framework so failing to include yii will give you this error if your tests depend on yii's unit test related classes.
Use the included bootstrap file and make sure you also include composer's autoload.php file. I normally add this to my yii config file (I believe by default, yii uses the test.php config file for custom testing related settings. You can include autoload.php inside this file)
Somewhere at the top of your yii config file
// Include composer autoload
require_once 'path/to/composer/vendor/autoload.php';
Your Composer autoloading is completely wrong.
Don't include the composer autoloader "vendor/autoload.php" in the autoloading of your own composer.json. There is only one autoloader, and it is created using your autoload data as well as any libraries.
The PSR-4 autoloading is also wrong. You have to state a directory where classes are put into files according to PSR-4. You point to one single file.
You don't use class prefixes for PSR-0 and PSR-4. This is bad for performance, because you are stating that ANY class could be in the directory. So Composer has to search for that class in this directory as well, even for classes that are guaranteed to not be there. Always use a prefix, and make it as long as possible to allow unique matches: One prefix should only ever point to exactly one directory structure.
You should explain why you don't use a bootstrap file in your tests. The minimal required bootstrap code for PHPUnit is to include the Composer autoloader. Additionally, you should add things that are required in your tests and have to be done once, like initialization of framework components -YMMV.

ZF2: autoloading libraries without namespaces

Previously I have only been using third party libraries that use namespaces together with Zend Framework 2. Now I need to use a library that does not use namespaces, and I cannot seem to make it work. I installed it via Composer, and it is installed fine into the vendor directory. I am trying to use it as follows:
$obj = new \SEOstats();
The result is a fatal error indicating that the class could not be found. I have tried to manually configure the StandardAutoloader, but so far without any luck. I thought that the autoloading would be done for me automatically when installing through Composer, but I guess that is not the case without namespaces? I haven't seen any reference to the library in the autoload files that Composer generated. I guess I have to do it manually - but how?
Thanks in advance.
You can use the files and classmap keys.
As an example consider this composer.json:
{
"require": {
"vendor-example/non-psr0-libraries": "dev-master",
},
"autoload":{
"files": ["vendor/vendor-example/non-psr0-libraries/Library01.php"]
}
}
Using the files key you can then use
$lib = new \Library01();
Use the classmap key when you need to load multiple files containing classes. The composer.json would be:
{
"require": {
"vendor-example/non-psr0-libraries": "dev-master",
},
"autoload":{
"classmap": ["vendor/vendor-example/non-psr0-libraries/"]
}
}
Composer will scan for .php and .inc files inside the specified directory configuring the autoloader for each file/class.
For more info you can check http://getcomposer.org/doc/04-schema.md#files and http://getcomposer.org/doc/04-schema.md#classmap
If you are under a namespace while creating the object, you must use the "\" (root namespace), otherwise you will use the Library01 class under the current namespace (if you have one, if you don't have one you'll get an error).

Silex custom class structure and loading

I'm building an app using Silex (the micro-framework).
As my app are growing in size and the need of using the same code in several routes rises, I want to organise everything a little more..
My idea is to create some custom classes, and then share them with my app:
$app['test'] = $app->share(function () {
require_once('../vendor/acme/src/test.php');
$testClass = new Test();
return new $testClass;
});
This actually works, but I need help with the following:
Autoload the class with composer (the way its supposed to be done in Silex).
Be able to use the existing Doctrine DBAL connection and methods within my class.
I hope someone can give me some tips how to get on, because I'm not finding the Silex docs very useful and I'm a beginner with both Silex and Composer.
Check the composer docs about autoloading, and when you added your config you should run composer dump-autoload to regenerate the composer autoloader. Then your require_once should not be necessary anymore.
Most likely this will work (assuming class Test is in src/Test.php):
{
"autoload": {
"psr-0": {
"": "src/"
}
}
}
This will make any PSR-0 compliant class inside src/ autoloadable.
Regarding your second point (using DBAL in your class), you should configure your class as a silex service that accesses the db service. Read up on services at http://silex.sensiolabs.org/doc/services.html

Custom code management with the Composer auto loader?

I've started a new project, where I use Composer to handle some dependencies, as well as their auto-loading.
I only keep the composer.json file in the VCS, instead of the entire vendor directory, so I don't want to start adding my code in there.
How should I handle my own project specific code, so that it auto loads as well?
This is actually very simple. Excluding vendors directory from your repository is the right approach. Your code should be stored in a separate place (like src).
Use the autoload property to make that composer recognizes your namespace(s):
{
"autoload": {
"psr-4": {
"Acme\\": "src/"
}
}
}
Assuming you have class names following the psr-4 standard, it should work. Below some example of class names and their locations on the file system:
Acme\Command\HelloCommand -> src/Command/HelloCommand.php
Acme\Form\Type\EmployeeType -> src/Form/Type/EmployeeType.php
Remember to define a namespace for each class. Here's an example of Acme\Command\HelloCommand:
<?php
namespace Acme\Command;
class HelloCommand
{
}
Don't forget to include the autoloader in your PHP controllers:
<?php
require 'vendor/autoload.php';
Read more on PSR-4 standard on PHP Framework Interoperability Group.
Note that if you edit composer.json, you need to either run install, update or dump-autoload to refresh the autoloader class paths.

Categories