Reinit composer autoload - php

After updating via the Composer i want to initialize the application and send it events
"scripts": {
"post-update-cmd": [
"Acme\\Bundle\\DemoBundle\\Composer\\ScriptHandler::notify"
handler
public static function notify(CommandEvent $event)
{
// init app
require __DIR__.'/../../../../../../app/autoload.php';
require __DIR__.'/../../../../../../app/AppKernel.php';
$kernel = new \AppKernel('dev', true);
$kernel->boot();
// send event
$dispatcher = $kernel->getContainer()->get('event_dispatcher');
$dispatcher->dispatch('acme.installed', new Event())
}
If run the update through the composer.phar then everything works fine.
But I need to run the update from the application. I add composer to requirements and call bin\composer update.
In this case there is a conflict of autoloader. Composer connects the autoloader from the application, change it, and does not connect it again.
Need to destroy the old and create a new autoloader. I found out that the old autoloader can be accessed via $GLOBALS['loader'].
I came to this decision
public static function notify(CommandEvent $event)
{
// init loader
require __DIR__.'/../../../../../../vendor/composer/autoload_real.php';
$GLOBALS['loader']->unregister();
$GLOBALS['loader'] = require __DIR__.'/../../../../../../app/autoload.php';
// init app
require_once __DIR__.'/../../../../../../app/AppKernel.php';
// ...
But this option does not work because autoloading via file broadcast Composer in normal require and leads to connection conflict.
For example:
"name": "kriswallsmith/assetic",
"autoload": {
"files": [ "src/functions.php" ]
},
translate to
require $vendorDir . '/kriswallsmith/assetic/src/functions.php';
throw error
PHP Fatal error: Cannot redeclare assetic_init() (previously declared in /vendor/kriswallsmith/assetic/src/functions.php:20) in /vendor/kriswallsmith/assetic/src/functions.php on line 26
I create autoloader and duplicate the code of /vendor/composer/autoload_real.php and /app/autoload.php. Recommendation from Seldaek #2474
public static function notify(CommandEvent $event)
{
if (isset($GLOBALS['loader']) && $GLOBALS['loader'] instanceof ClassLoader) {
$GLOBALS['loader']->unregister();
}
$GLOBALS['loader'] = $this->getClassLoader();
require_once __DIR__.'/../../../../../../app/AppKernel.php';
$kernel = new \AppKernel('dev', true);
$kernel->boot();
// send event
$dispatcher = $kernel->getContainer()->get('event_dispatcher');
$dispatcher->dispatch('acme.installed', new Event())
}
protected function getClassLoader()
{
$loader = new ClassLoader();
$vendorDir = __DIR__.'/../../../../../../vendor';
$baseDir = dirname($vendorDir);
$map = require $vendorDir . '/composer/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$classMap = require $vendorDir . '/composer/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
$loader->register(true);
$includeFiles = require $vendorDir . '/composer/autoload_files.php';
foreach ($includeFiles as $file) {
require_once $file;
}
// intl
if (!function_exists('intl_get_error_code')) {
require_once $vendorDir.'/symfony/symfony/src/Symfony/Component/Locale/Resources/stubs/functions.php';
$loader->add('', $vendorDir.'/symfony/symfony/src/Symfony/Component/Locale/Resources/stubs');
}
AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
return $loader;
}

I think I am a bit scared about what you are about to do: You want to update the components of a running application while these components are actively used. You hope that any update will run perfectly well so that after the update the application will continue to work - especially will continue to be able to do further updates.
I don't think this is a valid assumption! I have been using Composer for a while, and I have seen plenty of reasons why it did not update some parts of my dependencies, which most of the time were due to some network failure. Just think of using something from Github, and then Github is down.
What would happen then? You were probably able to download some parts, unable to download some more, and the autoloader was not updated. So the updated part now requires something new that got also downloaded, but cannot be autoloaded because some component after that failed to download. And this component is essential to repeat the update! You just broke your application, and you cannot fix it.
I can also think about very strange effects happening if the autoloader is partially loading old classes, then gets updated, and after that loads new classes that use new versions of the already loaded old classes that changed incompatible. So the assumption that you can change the components of the application during the runtime of one request seems to be very odd.
There is no way to unload a class in PHP. Once it is declared, it cannot be changed (not taking the "runkit" extension into account).
If you indeed want to get an update of your application, I think it is a better idea to duplicate everything, update the non-active copy of the application, then check if the update was successful, and after that copy it back, i.e. use symlinks to point to the current and next version and switch these.

Related

Composer Autoload only finding classes in root-level directories

I am currently working on building an API in the Slim framework, and having a bit of trouble with it not finding classes that should be getting autoloaded.
Folder structure
database
- db_connection.php
<?php
namespace Database;
class DBConnection {}
...
public
- index.php
src
- middlewares
-- authentication_middleware.php
<?php
namespace Middleware;
class AuthenticationMiddleware {}
...
- routes
-- different routes for tickets, projects, notifications etc.
- utils
-- data_convertor.php
<?php
namespace Utils;
class DataConvertor {}
...
And here is my composer.json:
{
"autoload": {
"psr-4": {
"Database\\": "./database",
"Middleware\\": "./src/middlewares",
"Utils\\": "./src/utils"
}
},
"require": {
// Dependencies
}
}
Now if I go into my projects.php file in src/routes and use Database\DBConnection, then do $db_connection = new DBConnection(); further down, all is well, my database connects and the app returns a response. However, now that I have attempted to create these other namespaces, Middleware and Utils, I am suddenly getting "Uncaught Error: Class "Middleware\AuthenticationMiddleware" not found". It is in Slim 4 so here is what it ultimately looks like:
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Database\DBConnection;
use Middleware\AuthenticationMiddleware;
$app->group('/projects', function($app) {
$app->get('', function(Request $request, Response $response, Array $args) {
try {
$db_connection = new DBConnection();
// The rest of the code
return $response->withStatus($status);
});
})->add(new AuthenticationMiddleware()); // Here is where the "error" is generated.
What on Earth could I be doing wrong? I have gone with the theory that Middleware might be a protected word so changed it to something else, to no avail, and even ended up renaming Middleware to MW and AuthenticationMiddleware to AM to eliminate any chance of mispelling, again to no avail. Between every step I have also been running composer update and even composer dumpautoload && composer update to try and get it working.
As the title may suggest, the one thing the non-loading ones have in common, and separate from the loading one, is that database is a top-level directory and the others aren't. But surely as long as the path is correct in composer.json that shouldn't matter?
Further to this, a bit more Googling before posting has led me to look at vendor/composer/autoload_psr4.php and sure enough, it returns the below:
return array(
'Utils\\' => array($baseDir . '/src/utils'),
'Middleware\\' => array($baseDir . '/src/middlewares'),
'Database\\' => array($baseDir . '/database'),
// Plenty of auto-generated ones with plenty of slashes in the directory paths
)
Thanks to the comments on my question I now know that the issue was that the class name e.g. AuthenticationMiddleware didn't match the filename, in this case authentication_middleware.php, changing that by renaming the files has fixed the issue. Weird that the database connection was still working despite that - but hey, it now all works so I'm good!

PHP - How to access Twilio in a composer namespace environment

I've been working with Rachet WebSockets and created a simple chat application. The example uses a WebSocket namespace. This is my first time using namespace. Now I'm trying to add Twilio service but can seem to add Twilio to my namespace.
I know it is autoloaded in the autoload_files.php
<?php
// autoload_files.php #generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
$vendorDir . '/twilio/sdk/Services/Twilio.php',
);
In the composer.json file
{
"autoload": {
"psr-0": {
"Websocket": "src"
}
},
"require": {
"cboden/ratchet": "^0.3.3",
"twilio/sdk": "^4.5"
}
}
I followed the steps from this website : https://www.twilio.com/docs/libraries/php#using-without-composer
I'm calling twilio inside a method of my class like this:
$AccountSid = "xxxxxxxxxxxxxxxxx";
$AuthToken = "xxxxxxxxxxxxxxxxx";
$client = new Client($sid, $token);
$message = $client->account->messages->create(array(
'To' => "+555555555",
'From' => "+555555555",
'Body' => "This is a test",
));
Keep getting this error: Uncaught Error: Class 'Websocket\Client' not found in ......
I'm very new to composer and namespace, hope this is enough information to help me.
I had to update Twilio,
ran composer require twilio/sd
- Removing twilio/sdk (4.12.0)
- Installing twilio/sdk (5.4.1)
Downloading: 100%
Now I'm able to use Twilio\Rest\Client; since it was missing before.
When using namespaces, PHP will always start looking for classes which aren't prepended with their own namespace in the current one.
In your case the current namespace would be Websocket, thus PHP is trying to autoload the class Websocket\Client, to prevent this, you have two options :
1) Tell PHP where to look by using use :
use Twilio\Rest\Client;
2) Prepend the correct namespace
$client = new \Twilio\Rest\Client($sid, $token);

symfony/event-dispatcher not receiving first dispatch

I'm using the symfony/event-dispatcher component and am trying to get a 'hello world' thing to work, I think.
My application (not symfony2) runs bootstrap.php then MVC's its way to some file I named composer.php.
bootstrap.php (the necessary parts)
<?php
require __DIR__ . '/vendor/autoload.php';
use Events\MsgEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
$dispatcher = new EventDispatcher();
if ($user_id) {
$dispatcher->dispatch('msg_event', new MsgEvent("I'm logged in as: " . $user['username']));
}
composer.php
<?php
use Events\MsgEvent;
$dispatcher->addListener('msg_event', function (MsgEvent $event) {
echo $event->getMsg();
});
$dispatcher->dispatch('msg_event', new MsgEvent("I got called from composer.php"));
On the route for my composer.php page I get to see 'called from composer', but my 'logged in as' message doesn't appear. When I output it's return, I can see it has succesfully created the event.
I have a feeling the problem is that the listener is defined after the first dispatch and somehow this does not get stored in the dispatcher. But I'm not sure.
What am I not seeing here?
You are dispatching the event before registering a listener. Since bootstrap.php is included before composer.php, the first event will fire with no listeners attached to it.

PHP Use Keyword Namespace Not Finding Class

Weirdness. Any ideas why it can't find the class?
Directory Tree:
test2.php
- src
- Google
- Spreadsheet
DefaultServiceRequest.php
ServiceRequestInterface.php
Google_Client.php
...
test2.php:
namespace src\Google\Spreadsheet;
require_once 'src/Google/Spreadsheet/ServiceRequestInterface.php';
require_once 'src/Google/Spreadsheet/DefaultServiceRequest.php';
require_once 'src/Google/Spreadsheet/Google_Client.php';
use Google\Spreadsheet\ServiceRequestInterface;
use Google\Spreadsheet\DefaultServiceRequest;
use Google\Spreadsheet\ServiceRequestFactory;
function getGoogleTokenFromKeyFile($clientId, $clientEmail, $pathToP12File) {
require 'src/Google/Spreadsheet/Google_Client.php';
$client = new Google_Client();
$client->setClientId($clientId);
$cred = new Google_Auth_AssertionCredentials(
$clientEmail,
array('https://spreadsheets.google.com/feeds'),
file_get_contents($pathToP12File)
);
$client->setAssertionCredentials($cred);
if ($client->getAuth()->isAccessTokenExpired()) {
$client->getAuth()->refreshTokenWithAssertion($cred);
}
$service_token = json_decode($client->getAccessToken());
return $service_token->access_token;
}
$serviceRequest = new DefaultServiceRequest(getGoogleTokenFromKeyFile(..., ..., ...));
ServiceRequestFactory::setInstance($serviceRequest);
Not sure if this is Google API related or what. Something weird is the ServiceRequest classes wouldn't work until I required them. When I didn't, it said it couldn't find it... And when I tried adding src/ to the use path, didn't work, and I tried removing the path all together, all did nothing.
Error: Fatal error: Class 'src\Google\Spreadsheet\Google_Client' not found in test2.php on line 15
looks like you are using this library
if you used composer to install the library you need to include the vendor/autoload.php file in your code
require 'vendor/autoload.php';
it is recomneded that you use composer to install this library but if you don't want to use composer, you need to create autoloader and require it in your code

Using FirePHP with Zend Framework 2

I'm trying to use FirePHP with Zend Framework 2, but there seems to be something missing. Here's the basic code I'm trying to run:
$writer = new Zend\Log\Writer\FirePhp();
$logger = new Zend\Log\Logger();
$logger->addWriter($writer);
$logger->info('FirePHP logging enabled');
The error I get is "FirePHP Class not found". I was initially puzzled because I do have a FirePhp class in my Zend/Log/Writer folder. But then I saw that the class constructor requires a FirePhp\FirePhpInterface object. So I checked the Zend/Log/Writer/FirePhp folder and there's a FirePhpBridge class in there that implements FirePhpInterface, but it also requires a FirePHP instance in the constructor. I don't have any FirePHP.php file in my Zend/Log/Writer/FirePhp folder. Am I supposed to get this from somewhere else?
Update
I now have managed to get FirePHP working, but I'm trying to figure out how to do it in a clean way so this works. The only way I've gotten it to work is putting it in the root directory of my project and doing the following:
include_once('FirePHP.php');
$writer = new Zend\Log\Writer\FirePhp(new Zend\Log\Writer\FirePhp\FirePhpBridge(FirePHP::getInstance(true)));
$logger = new Zend\Log\Logger();
$logger->addWriter($writer);
$logger->info('FirePHP logging enabled');
I assume that normally I should be able to create a writer like so:
$writer = new Zend\Log\Writer\FirePhp();
However, where this goes wrong I believe is in the getFirePhp() function of the Zend\Log\Writer\FirePhp class. The class does this:
if (!$this->firephp instanceof FirePhp\FirePhpInterface
&& !class_exists('FirePHP')
) {
// No FirePHP instance, and no way to create one
throw new Exception\RuntimeException('FirePHP Class not found');
}
// Remember: class names in strings are absolute; thus the class_exists
// here references the canonical name for the FirePHP class
if (!$this->firephp instanceof FirePhp\FirePhpInterface
&& class_exists('FirePHP')
) {
// FirePHPService is an alias for FirePHP; otherwise the class
// names would clash in this file on this line.
$this->setFirePhp(new FirePhp\FirePhpBridge(new FirePHPService()));
}
This is where I get lost as to how I'm supposed to set things up so that this class_exists('FirePHP') call finds the right class and new FirePHPService() also works properly.
First you should add this code to Module.php of your module
return array(
//...
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
);
and here content of autoload_classmap.php
<?php
return array(
'FirePHP' => realpath(APPLICATION_PATH . '/vendor/FirePHP').'/FirePHP.php',
);
FirePHP.php(renamed from FirePHP.class.php) downloaded from official site.
then you can write below code in any place of your module and it will work
use Zend\Log\Writer\FirePhp;
use Zend\Log\Logger;
$writer = new FirePhp();
$logger = new Logger();
$logger->addWriter($writer);
$logger->info("hi");
Am I supposed to get this from somewhere else?
Yes, you need to get FirePHP into your project and autoloading.
If you're using composer (and I recommend that you do), just add:
"firephp/firephp-core" : "dev-master"
(or similar) in your composer.json and update. If you're not using composer, you should grab the firephp libs, and let your autoloader know about them.

Categories