在ZF3应用程序的功能PHPUnit测试中,如何关闭数据库连接并减少它们的数量?
几年前,我已经在StackOverflow上创建了一个非常相似甚至几乎相同的。我得到了非常详细的答案,但并没有解决我的问题。现在,问题变得更大了,我开始第二次尝试解决这个问题。由于代码已更改,我不想更新原始问题。这将是一个太大的更新,答案可能与问题的新版本不匹配。因此,我将其表述为一个新的: 我正在使用在ZF3应用程序的功能PHPUnit测试中,如何关闭数据库连接并减少它们的数量?,phpunit,integration-testing,functional-testing,zend-framework3,database-testing,Phpunit,Integration Testing,Functional Testing,Zend Framework3,Database Testing,几年前,我已经在StackOverflow上创建了一个非常相似甚至几乎相同的。我得到了非常详细的答案,但并没有解决我的问题。现在,问题变得更大了,我开始第二次尝试解决这个问题。由于代码已更改,我不想更新原始问题。这将是一个太大的更新,答案可能与问题的新版本不匹配。因此,我将其表述为一个新的: 我正在使用 zendframework/zend测试3.2.2 phpunit/phpunit6.5.14,以及 phpunit/dbunit3.0.3 大多数测试都是一种控制器测试。测试代码通过使用Z
- zendframework/zend测试
3.2.2
- phpunit/phpunit
,以及6.5.14
- phpunit/dbunit
3.0.3
Zend\test\PHPUnit\Controller\AbstractControllerTestCase#dispatch(…)
调用URI/操作并分析1。响应/输出数据和2。数据库中的更改(如果是像“create foo”这样的编写调用),例如:
对于每个测试,数据库都会被重置
问题是,随着下一次测试的进行,数据库连接的数量不断增加。目前大约有350个用于102
测试的连接(显示全局状态,如'max_used_connections';
)。(作为一种解决方法,我必须越来越多地增加MySQL的max\u连接数。)
我试图通过放置逻辑来减少连接的数量,比如$this->dbAdapter->getDriver()->getConnection()->disconnect()代码>或/和$this->entityManager->getConnection()->close()代码>到控制器测试的我的超类的tearDown()
。通过这种方式,我将连接的数量减少了大约90个。但是大部分的连接仍然没有被杀死
在ZF3应用程序的功能/控制器PHPUnit测试中,如何关闭DB连接并显著减少并发打开的连接数
附加信息:我的代码中最相关的部分
AbstractControllerTest
namespace Base\Test;
use Doctrine\ORM\EntityManager;
use PDO;
use PHPUnit\DbUnit\Database\DefaultConnection;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Sql;
use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
/**
* Class AbstractControllerTest
*
* @package Base\Test
*/
abstract class AbstractControllerTest extends AbstractHttpControllerTestCase
{
use DatabaseConnectionTrait;
/**
* @var string
*/
static private $applicationConfigPath;
/** @var Adapter */
protected $dbAdapter;
/** @var EntityManager */
protected $entityManager;
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
$this->setApplicationConfig(include self::$applicationConfigPath);
}
public static function setApplicationConfigPath(string $applicationConfigPath)
{
self::$applicationConfigPath = $applicationConfigPath;
}
protected function tearDown()
{
// Connections: 354
// Time: 5.7 minutes, Memory: 622.00MB
// OK (102 tests, 367 assertions)
// no optimization
// Connections: 326 (26 connections less)
// Time: 5.86 minutes, Memory: 620.00MB
// OK (102 tests, 367 assertions)
// if ($this->dbAdapter && $this->dbAdapter instanceof Adapter) {
// $this->dbAdapter->getDriver()->getConnection()->disconnect();
// }
// Connections: 354
// Time: 5.67 minutes, Memory: 620.00MB
// OK (102 tests, 367 assertions)
// $this->entityManager->close();
// Connections: 291 (63 connections less)
// Time: 5.63 minutes, Memory: 622.00MB
// OK (102 tests, 367 assertions)
// $this->entityManager->getConnection()->close();
// Connections: 264 (90 connections less)
// Time: 5.7 minutes, Memory: 620.00MB
// OK (102 tests, 367 assertions)
// if ($this->dbAdapter && $this->dbAdapter instanceof Adapter) {
// $this->dbAdapter->getDriver()->getConnection()->disconnect();
// }
// $this->entityManager->getConnection()->close();
// Connections: 251
// Time: 4.71 minutes, Memory: 574.00MB
// OK (102 tests, 367 assertions)
// After removing initialization of the EntityManager and the DbAdapter in the constructor and the setUp().
// closing DB connections
if ($this->dbAdapter && $this->dbAdapter instanceof Adapter) {
$this->dbAdapter->getDriver()->getConnection()->disconnect();
}
if ($this->entityManager && $this->entityManager instanceof EntityManager) {
$this->entityManager->getConnection()->close();
}
$reflectionObject = new \ReflectionObject($this);
foreach ($reflectionObject->getProperties() as $prop) {
if (!$prop->isStatic() && 0 !== strpos($prop->getDeclaringClass()->getName(), 'PHPUnit_')) {
$prop->setAccessible(true);
$prop->setValue($this, null);
}
}
$this->reset();
$this->application = null;
gc_collect_cycles();
unset($_SERVER['AUTH_USER']);
parent::tearDown();
}
protected function retrieveActualData($table, $idColumn, $idValue)
{
$sql = new Sql($this->getDbAdapter());
$select = $sql->select($table);
$select->where([$table . '.' . $idColumn . ' = ?' => $idValue]);
$statement = $sql->prepareStatementForSqlObject($select);
$result = $statement->execute();
$data = $result->current();
// Decreases the total number of the connections by 1 less.
// $this->dbAdapter->getDriver()->getConnection()->disconnect();
return $data;
}
protected function getEntityManager()
{
$this->entityManager = $this->entityManager
?: $this->getApplicationServiceLocator()->get('doctrine.entitymanager.orm_default')
;
return $this->entityManager;
}
protected function getDbAdapter()
{
$this->dbAdapter = $this->dbAdapter
?: $this->getApplicationServiceLocator()->get('Zend\Db\Adapter\Adapter')
;
return $this->dbAdapter;
}
}
namespace Base\Test;
use PDO;
use PHPUnit\DbUnit\Database\Connection;
use PHPUnit\DbUnit\Database\DefaultConnection;
use PHPUnit\DbUnit\InvalidArgumentException;
trait DatabaseConnectionTrait
{
/**
* @var array
*/
static private $dbConfigs;
/**
* @var PDO
*/
static private $pdo;
/**
* @var Connection
*/
private $connection;
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
}
/**
* @return Connection
*/
public function getConnection()
{
if (! $this->connection) {
if (! self::$dbConfigs) {
throw new InvalidArgumentException(
'Set the database configuration first.'
. ' '. 'Use the ' . self::class . '::setDbConfigs(...).'
);
}
if (! self::$pdo) {
self::$pdo = new PDO(
self::$dbConfigs['dsn'],
self::$dbConfigs['username'],
self::$dbConfigs['password'],
[PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'']
);
}
$this->connection = $this->createDefaultDBConnection(self::$pdo, self::$dbConfigs['database']);
}
return $this->connection;
}
public static function setDbConfigs(array $dbConfigs)
{
self::$dbConfigs = $dbConfigs;
}
/**
* Creates a new DefaultDatabaseConnection using the given PDO connection
* and database schema name.
*
* @see The original PHPUnit\DbUnit\TestCaseTrait#createDefaultDBConnection(...).
*
* @param PDO $connection
* @param string $schema
*
* @return DefaultConnection
*/
protected function createDefaultDBConnection(PDO $connection, $schema = '')
{
return new DefaultConnection($connection, $schema);
}
}
namespace Base\Test;
/**
* Class DatabaseInitializer
*
* @package Base\Test
*/
class DatabaseInitializer
{
use DatabaseConnectionTrait;
/**
* @var string
*/
private $database;
public function __construct(array $dbConfigs)
{
$this->database = $dbConfigs['database'];
self::$dbConfigs = $dbConfigs;
}
public function setUp()
{
$schemaSql = file_get_contents(self::$dbConfigs['scripts']['schema']);
$storedProceduresSql = file_get_contents(self::$dbConfigs['scripts']['stored-procedures']);
$basicDataSql = file_get_contents(self::$dbConfigs['scripts']['basic-data']);
$testDataSqlSet = array_map(function ($sqlFile) {
return file_get_contents($sqlFile);
}, self::$dbConfigs['scripts']['test-data']);
$this->dropDatabase();
$this->createDatabase();
$this->useDatabase();
$this->createSchema($schemaSql);
$this->createStoredProcedures($storedProceduresSql);
$this->createBasicData($basicDataSql);
$this->createTestData($testDataSqlSet);
}
public function tearDown()
{
self::$pdo = null;
}
protected function createDatabase()
{
$this->getDatabaseConnection()->exec('CREATE DATABASE IF NOT EXISTS ' . $this->database . ';');
}
protected function useDatabase()
{
$this->getDatabaseConnection()->exec('USE ' . $this->database . ';');
}
protected function createSchema(string $sql)
{
$this->getDatabaseConnection()->exec($sql);
}
protected function createBasicData(string $sql)
{
$this->getDatabaseConnection()->exec($sql);
}
protected function createTestData(array $sqlSet = [])
{
foreach ($sqlSet as $sql) {
$this->getDatabaseConnection()->exec($sql);
}
}
protected function createStoredProcedures(string $sql)
{
$statement = $this->getDatabaseConnection()->prepare($sql);
$statement->execute();
}
protected function dropDatabase()
{
$this->getDatabaseConnection()->exec('DROP DATABASE IF EXISTS ' . $this->database . ';');
}
protected function getDatabaseConnection()
{
return $this->getConnection()->getConnection();
}
}
namespace Test;
use Base\Test\AbstractControllerTest;
use Base\Test\AbstractDbTest;
use Base\Test\DatabaseInitializer;
use Doctrine\ORM\EntityManager;
use RuntimeException;
use Zend\Loader\AutoloaderFactory;
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
error_reporting(E_ALL | E_STRICT);
ini_set('memory_limit', '2048M');
chdir(__DIR__);
/**
* Sets up the MVC (application, service manager, autoloading) and the database.
*/
class Bootstrap
{
/** @var ServiceManager */
protected $serviceManager;
protected $applicationConfigPath;
/** @var EntityManager */
protected $entityManager;
public function __construct()
{
$this->applicationConfigPath = __DIR__ . '/../config/application.config.php';
}
/**
* Sets up the
*/
public function init()
{
// autoloading setup
static::initAutoloader();
// application configuration & setup
$applicationConfig = require_once $this->applicationConfigPath;
$this->prepareApplication($applicationConfig);
// database configuration & setup
$dbConfigs = $this->serviceManager->get('Config')['db'];
$this->setUpDatabase($dbConfigs);
// listeners & application bootstrap
$listeners = $this->prepareListeners();
$this->bootstrapApplication($listeners);
}
public function chroot()
{
$rootPath = dirname(static::findParentPath('module'));
chdir($rootPath);
}
protected function prepareApplication($config)
{
$serviceManagerConfig = isset($config['service_manager']) ? $config['service_manager'] : [];
$serviceManagerConfigObject = new ServiceManagerConfig($serviceManagerConfig);
$this->serviceManager = new ServiceManager();
$serviceManagerConfigObject->configureServiceManager($this->serviceManager);
$this->serviceManager->setService('ApplicationConfig', $config);
$this->serviceManager->get('ModuleManager')->loadModules();
}
protected function prepareListeners()
{
$listenersFromAppConfig = [];
$config = $this->serviceManager->get('config');
$listenersFromConfigService = isset($config['listeners']) ? $config['listeners'] : [];
$listeners = array_unique(array_merge($listenersFromConfigService, $listenersFromAppConfig));
return $listeners;
}
protected function bootstrapApplication($listeners)
{
$application = $this->serviceManager->get('Application');
$application->bootstrap($listeners);
}
protected function setUpDatabase(array $dbConfigs)
{
$databaseInitializer = new DatabaseInitializer($dbConfigs);
$databaseInitializer->setUp();
AbstractDbTest::setDbConfigs($dbConfigs);
AbstractControllerTest::setApplicationConfigPath($this->applicationConfigPath);
AbstractControllerTest::setDbConfigs($dbConfigs);
}
protected function initAutoloader()
{
$vendorPath = static::findParentPath('vendor');
if (file_exists($vendorPath.'/autoload.php')) {
include $vendorPath.'/autoload.php';
}
if (! class_exists('Zend\Loader\AutoloaderFactory')) {
throw new RuntimeException(
'Unable to load ZF2. Run `php composer.phar install`'
);
}
AutoloaderFactory::factory(array(
'Zend\Loader\StandardAutoloader' => array(
'autoregister_zf' => true,
'namespaces' => array(
__NAMESPACE__ => __DIR__,
),
),
));
}
protected function findParentPath($path)
{
$dir = __DIR__;
$previousDir = '.';
while (!is_dir($dir . '/' . $path)) {
$dir = dirname($dir);
if ($previousDir === $dir) {
return false;
}
$previousDir = $dir;
}
return $dir . '/' . $path;
}
}
$bootstrap = new Bootstrap();
$bootstrap->init();
$bootstrap->chroot();
DatabaseConnectionTrait
namespace Base\Test;
use Doctrine\ORM\EntityManager;
use PDO;
use PHPUnit\DbUnit\Database\DefaultConnection;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Sql;
use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
/**
* Class AbstractControllerTest
*
* @package Base\Test
*/
abstract class AbstractControllerTest extends AbstractHttpControllerTestCase
{
use DatabaseConnectionTrait;
/**
* @var string
*/
static private $applicationConfigPath;
/** @var Adapter */
protected $dbAdapter;
/** @var EntityManager */
protected $entityManager;
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
$this->setApplicationConfig(include self::$applicationConfigPath);
}
public static function setApplicationConfigPath(string $applicationConfigPath)
{
self::$applicationConfigPath = $applicationConfigPath;
}
protected function tearDown()
{
// Connections: 354
// Time: 5.7 minutes, Memory: 622.00MB
// OK (102 tests, 367 assertions)
// no optimization
// Connections: 326 (26 connections less)
// Time: 5.86 minutes, Memory: 620.00MB
// OK (102 tests, 367 assertions)
// if ($this->dbAdapter && $this->dbAdapter instanceof Adapter) {
// $this->dbAdapter->getDriver()->getConnection()->disconnect();
// }
// Connections: 354
// Time: 5.67 minutes, Memory: 620.00MB
// OK (102 tests, 367 assertions)
// $this->entityManager->close();
// Connections: 291 (63 connections less)
// Time: 5.63 minutes, Memory: 622.00MB
// OK (102 tests, 367 assertions)
// $this->entityManager->getConnection()->close();
// Connections: 264 (90 connections less)
// Time: 5.7 minutes, Memory: 620.00MB
// OK (102 tests, 367 assertions)
// if ($this->dbAdapter && $this->dbAdapter instanceof Adapter) {
// $this->dbAdapter->getDriver()->getConnection()->disconnect();
// }
// $this->entityManager->getConnection()->close();
// Connections: 251
// Time: 4.71 minutes, Memory: 574.00MB
// OK (102 tests, 367 assertions)
// After removing initialization of the EntityManager and the DbAdapter in the constructor and the setUp().
// closing DB connections
if ($this->dbAdapter && $this->dbAdapter instanceof Adapter) {
$this->dbAdapter->getDriver()->getConnection()->disconnect();
}
if ($this->entityManager && $this->entityManager instanceof EntityManager) {
$this->entityManager->getConnection()->close();
}
$reflectionObject = new \ReflectionObject($this);
foreach ($reflectionObject->getProperties() as $prop) {
if (!$prop->isStatic() && 0 !== strpos($prop->getDeclaringClass()->getName(), 'PHPUnit_')) {
$prop->setAccessible(true);
$prop->setValue($this, null);
}
}
$this->reset();
$this->application = null;
gc_collect_cycles();
unset($_SERVER['AUTH_USER']);
parent::tearDown();
}
protected function retrieveActualData($table, $idColumn, $idValue)
{
$sql = new Sql($this->getDbAdapter());
$select = $sql->select($table);
$select->where([$table . '.' . $idColumn . ' = ?' => $idValue]);
$statement = $sql->prepareStatementForSqlObject($select);
$result = $statement->execute();
$data = $result->current();
// Decreases the total number of the connections by 1 less.
// $this->dbAdapter->getDriver()->getConnection()->disconnect();
return $data;
}
protected function getEntityManager()
{
$this->entityManager = $this->entityManager
?: $this->getApplicationServiceLocator()->get('doctrine.entitymanager.orm_default')
;
return $this->entityManager;
}
protected function getDbAdapter()
{
$this->dbAdapter = $this->dbAdapter
?: $this->getApplicationServiceLocator()->get('Zend\Db\Adapter\Adapter')
;
return $this->dbAdapter;
}
}
namespace Base\Test;
use PDO;
use PHPUnit\DbUnit\Database\Connection;
use PHPUnit\DbUnit\Database\DefaultConnection;
use PHPUnit\DbUnit\InvalidArgumentException;
trait DatabaseConnectionTrait
{
/**
* @var array
*/
static private $dbConfigs;
/**
* @var PDO
*/
static private $pdo;
/**
* @var Connection
*/
private $connection;
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
}
/**
* @return Connection
*/
public function getConnection()
{
if (! $this->connection) {
if (! self::$dbConfigs) {
throw new InvalidArgumentException(
'Set the database configuration first.'
. ' '. 'Use the ' . self::class . '::setDbConfigs(...).'
);
}
if (! self::$pdo) {
self::$pdo = new PDO(
self::$dbConfigs['dsn'],
self::$dbConfigs['username'],
self::$dbConfigs['password'],
[PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'']
);
}
$this->connection = $this->createDefaultDBConnection(self::$pdo, self::$dbConfigs['database']);
}
return $this->connection;
}
public static function setDbConfigs(array $dbConfigs)
{
self::$dbConfigs = $dbConfigs;
}
/**
* Creates a new DefaultDatabaseConnection using the given PDO connection
* and database schema name.
*
* @see The original PHPUnit\DbUnit\TestCaseTrait#createDefaultDBConnection(...).
*
* @param PDO $connection
* @param string $schema
*
* @return DefaultConnection
*/
protected function createDefaultDBConnection(PDO $connection, $schema = '')
{
return new DefaultConnection($connection, $schema);
}
}
namespace Base\Test;
/**
* Class DatabaseInitializer
*
* @package Base\Test
*/
class DatabaseInitializer
{
use DatabaseConnectionTrait;
/**
* @var string
*/
private $database;
public function __construct(array $dbConfigs)
{
$this->database = $dbConfigs['database'];
self::$dbConfigs = $dbConfigs;
}
public function setUp()
{
$schemaSql = file_get_contents(self::$dbConfigs['scripts']['schema']);
$storedProceduresSql = file_get_contents(self::$dbConfigs['scripts']['stored-procedures']);
$basicDataSql = file_get_contents(self::$dbConfigs['scripts']['basic-data']);
$testDataSqlSet = array_map(function ($sqlFile) {
return file_get_contents($sqlFile);
}, self::$dbConfigs['scripts']['test-data']);
$this->dropDatabase();
$this->createDatabase();
$this->useDatabase();
$this->createSchema($schemaSql);
$this->createStoredProcedures($storedProceduresSql);
$this->createBasicData($basicDataSql);
$this->createTestData($testDataSqlSet);
}
public function tearDown()
{
self::$pdo = null;
}
protected function createDatabase()
{
$this->getDatabaseConnection()->exec('CREATE DATABASE IF NOT EXISTS ' . $this->database . ';');
}
protected function useDatabase()
{
$this->getDatabaseConnection()->exec('USE ' . $this->database . ';');
}
protected function createSchema(string $sql)
{
$this->getDatabaseConnection()->exec($sql);
}
protected function createBasicData(string $sql)
{
$this->getDatabaseConnection()->exec($sql);
}
protected function createTestData(array $sqlSet = [])
{
foreach ($sqlSet as $sql) {
$this->getDatabaseConnection()->exec($sql);
}
}
protected function createStoredProcedures(string $sql)
{
$statement = $this->getDatabaseConnection()->prepare($sql);
$statement->execute();
}
protected function dropDatabase()
{
$this->getDatabaseConnection()->exec('DROP DATABASE IF EXISTS ' . $this->database . ';');
}
protected function getDatabaseConnection()
{
return $this->getConnection()->getConnection();
}
}
namespace Test;
use Base\Test\AbstractControllerTest;
use Base\Test\AbstractDbTest;
use Base\Test\DatabaseInitializer;
use Doctrine\ORM\EntityManager;
use RuntimeException;
use Zend\Loader\AutoloaderFactory;
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
error_reporting(E_ALL | E_STRICT);
ini_set('memory_limit', '2048M');
chdir(__DIR__);
/**
* Sets up the MVC (application, service manager, autoloading) and the database.
*/
class Bootstrap
{
/** @var ServiceManager */
protected $serviceManager;
protected $applicationConfigPath;
/** @var EntityManager */
protected $entityManager;
public function __construct()
{
$this->applicationConfigPath = __DIR__ . '/../config/application.config.php';
}
/**
* Sets up the
*/
public function init()
{
// autoloading setup
static::initAutoloader();
// application configuration & setup
$applicationConfig = require_once $this->applicationConfigPath;
$this->prepareApplication($applicationConfig);
// database configuration & setup
$dbConfigs = $this->serviceManager->get('Config')['db'];
$this->setUpDatabase($dbConfigs);
// listeners & application bootstrap
$listeners = $this->prepareListeners();
$this->bootstrapApplication($listeners);
}
public function chroot()
{
$rootPath = dirname(static::findParentPath('module'));
chdir($rootPath);
}
protected function prepareApplication($config)
{
$serviceManagerConfig = isset($config['service_manager']) ? $config['service_manager'] : [];
$serviceManagerConfigObject = new ServiceManagerConfig($serviceManagerConfig);
$this->serviceManager = new ServiceManager();
$serviceManagerConfigObject->configureServiceManager($this->serviceManager);
$this->serviceManager->setService('ApplicationConfig', $config);
$this->serviceManager->get('ModuleManager')->loadModules();
}
protected function prepareListeners()
{
$listenersFromAppConfig = [];
$config = $this->serviceManager->get('config');
$listenersFromConfigService = isset($config['listeners']) ? $config['listeners'] : [];
$listeners = array_unique(array_merge($listenersFromConfigService, $listenersFromAppConfig));
return $listeners;
}
protected function bootstrapApplication($listeners)
{
$application = $this->serviceManager->get('Application');
$application->bootstrap($listeners);
}
protected function setUpDatabase(array $dbConfigs)
{
$databaseInitializer = new DatabaseInitializer($dbConfigs);
$databaseInitializer->setUp();
AbstractDbTest::setDbConfigs($dbConfigs);
AbstractControllerTest::setApplicationConfigPath($this->applicationConfigPath);
AbstractControllerTest::setDbConfigs($dbConfigs);
}
protected function initAutoloader()
{
$vendorPath = static::findParentPath('vendor');
if (file_exists($vendorPath.'/autoload.php')) {
include $vendorPath.'/autoload.php';
}
if (! class_exists('Zend\Loader\AutoloaderFactory')) {
throw new RuntimeException(
'Unable to load ZF2. Run `php composer.phar install`'
);
}
AutoloaderFactory::factory(array(
'Zend\Loader\StandardAutoloader' => array(
'autoregister_zf' => true,
'namespaces' => array(
__NAMESPACE__ => __DIR__,
),
),
));
}
protected function findParentPath($path)
{
$dir = __DIR__;
$previousDir = '.';
while (!is_dir($dir . '/' . $path)) {
$dir = dirname($dir);
if ($previousDir === $dir) {
return false;
}
$previousDir = $dir;
}
return $dir . '/' . $path;
}
}
$bootstrap = new Bootstrap();
$bootstrap->init();
$bootstrap->chroot();
数据库初始值设定项
namespace Base\Test;
use Doctrine\ORM\EntityManager;
use PDO;
use PHPUnit\DbUnit\Database\DefaultConnection;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Sql;
use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
/**
* Class AbstractControllerTest
*
* @package Base\Test
*/
abstract class AbstractControllerTest extends AbstractHttpControllerTestCase
{
use DatabaseConnectionTrait;
/**
* @var string
*/
static private $applicationConfigPath;
/** @var Adapter */
protected $dbAdapter;
/** @var EntityManager */
protected $entityManager;
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
$this->setApplicationConfig(include self::$applicationConfigPath);
}
public static function setApplicationConfigPath(string $applicationConfigPath)
{
self::$applicationConfigPath = $applicationConfigPath;
}
protected function tearDown()
{
// Connections: 354
// Time: 5.7 minutes, Memory: 622.00MB
// OK (102 tests, 367 assertions)
// no optimization
// Connections: 326 (26 connections less)
// Time: 5.86 minutes, Memory: 620.00MB
// OK (102 tests, 367 assertions)
// if ($this->dbAdapter && $this->dbAdapter instanceof Adapter) {
// $this->dbAdapter->getDriver()->getConnection()->disconnect();
// }
// Connections: 354
// Time: 5.67 minutes, Memory: 620.00MB
// OK (102 tests, 367 assertions)
// $this->entityManager->close();
// Connections: 291 (63 connections less)
// Time: 5.63 minutes, Memory: 622.00MB
// OK (102 tests, 367 assertions)
// $this->entityManager->getConnection()->close();
// Connections: 264 (90 connections less)
// Time: 5.7 minutes, Memory: 620.00MB
// OK (102 tests, 367 assertions)
// if ($this->dbAdapter && $this->dbAdapter instanceof Adapter) {
// $this->dbAdapter->getDriver()->getConnection()->disconnect();
// }
// $this->entityManager->getConnection()->close();
// Connections: 251
// Time: 4.71 minutes, Memory: 574.00MB
// OK (102 tests, 367 assertions)
// After removing initialization of the EntityManager and the DbAdapter in the constructor and the setUp().
// closing DB connections
if ($this->dbAdapter && $this->dbAdapter instanceof Adapter) {
$this->dbAdapter->getDriver()->getConnection()->disconnect();
}
if ($this->entityManager && $this->entityManager instanceof EntityManager) {
$this->entityManager->getConnection()->close();
}
$reflectionObject = new \ReflectionObject($this);
foreach ($reflectionObject->getProperties() as $prop) {
if (!$prop->isStatic() && 0 !== strpos($prop->getDeclaringClass()->getName(), 'PHPUnit_')) {
$prop->setAccessible(true);
$prop->setValue($this, null);
}
}
$this->reset();
$this->application = null;
gc_collect_cycles();
unset($_SERVER['AUTH_USER']);
parent::tearDown();
}
protected function retrieveActualData($table, $idColumn, $idValue)
{
$sql = new Sql($this->getDbAdapter());
$select = $sql->select($table);
$select->where([$table . '.' . $idColumn . ' = ?' => $idValue]);
$statement = $sql->prepareStatementForSqlObject($select);
$result = $statement->execute();
$data = $result->current();
// Decreases the total number of the connections by 1 less.
// $this->dbAdapter->getDriver()->getConnection()->disconnect();
return $data;
}
protected function getEntityManager()
{
$this->entityManager = $this->entityManager
?: $this->getApplicationServiceLocator()->get('doctrine.entitymanager.orm_default')
;
return $this->entityManager;
}
protected function getDbAdapter()
{
$this->dbAdapter = $this->dbAdapter
?: $this->getApplicationServiceLocator()->get('Zend\Db\Adapter\Adapter')
;
return $this->dbAdapter;
}
}
namespace Base\Test;
use PDO;
use PHPUnit\DbUnit\Database\Connection;
use PHPUnit\DbUnit\Database\DefaultConnection;
use PHPUnit\DbUnit\InvalidArgumentException;
trait DatabaseConnectionTrait
{
/**
* @var array
*/
static private $dbConfigs;
/**
* @var PDO
*/
static private $pdo;
/**
* @var Connection
*/
private $connection;
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
}
/**
* @return Connection
*/
public function getConnection()
{
if (! $this->connection) {
if (! self::$dbConfigs) {
throw new InvalidArgumentException(
'Set the database configuration first.'
. ' '. 'Use the ' . self::class . '::setDbConfigs(...).'
);
}
if (! self::$pdo) {
self::$pdo = new PDO(
self::$dbConfigs['dsn'],
self::$dbConfigs['username'],
self::$dbConfigs['password'],
[PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'']
);
}
$this->connection = $this->createDefaultDBConnection(self::$pdo, self::$dbConfigs['database']);
}
return $this->connection;
}
public static function setDbConfigs(array $dbConfigs)
{
self::$dbConfigs = $dbConfigs;
}
/**
* Creates a new DefaultDatabaseConnection using the given PDO connection
* and database schema name.
*
* @see The original PHPUnit\DbUnit\TestCaseTrait#createDefaultDBConnection(...).
*
* @param PDO $connection
* @param string $schema
*
* @return DefaultConnection
*/
protected function createDefaultDBConnection(PDO $connection, $schema = '')
{
return new DefaultConnection($connection, $schema);
}
}
namespace Base\Test;
/**
* Class DatabaseInitializer
*
* @package Base\Test
*/
class DatabaseInitializer
{
use DatabaseConnectionTrait;
/**
* @var string
*/
private $database;
public function __construct(array $dbConfigs)
{
$this->database = $dbConfigs['database'];
self::$dbConfigs = $dbConfigs;
}
public function setUp()
{
$schemaSql = file_get_contents(self::$dbConfigs['scripts']['schema']);
$storedProceduresSql = file_get_contents(self::$dbConfigs['scripts']['stored-procedures']);
$basicDataSql = file_get_contents(self::$dbConfigs['scripts']['basic-data']);
$testDataSqlSet = array_map(function ($sqlFile) {
return file_get_contents($sqlFile);
}, self::$dbConfigs['scripts']['test-data']);
$this->dropDatabase();
$this->createDatabase();
$this->useDatabase();
$this->createSchema($schemaSql);
$this->createStoredProcedures($storedProceduresSql);
$this->createBasicData($basicDataSql);
$this->createTestData($testDataSqlSet);
}
public function tearDown()
{
self::$pdo = null;
}
protected function createDatabase()
{
$this->getDatabaseConnection()->exec('CREATE DATABASE IF NOT EXISTS ' . $this->database . ';');
}
protected function useDatabase()
{
$this->getDatabaseConnection()->exec('USE ' . $this->database . ';');
}
protected function createSchema(string $sql)
{
$this->getDatabaseConnection()->exec($sql);
}
protected function createBasicData(string $sql)
{
$this->getDatabaseConnection()->exec($sql);
}
protected function createTestData(array $sqlSet = [])
{
foreach ($sqlSet as $sql) {
$this->getDatabaseConnection()->exec($sql);
}
}
protected function createStoredProcedures(string $sql)
{
$statement = $this->getDatabaseConnection()->prepare($sql);
$statement->execute();
}
protected function dropDatabase()
{
$this->getDatabaseConnection()->exec('DROP DATABASE IF EXISTS ' . $this->database . ';');
}
protected function getDatabaseConnection()
{
return $this->getConnection()->getConnection();
}
}
namespace Test;
use Base\Test\AbstractControllerTest;
use Base\Test\AbstractDbTest;
use Base\Test\DatabaseInitializer;
use Doctrine\ORM\EntityManager;
use RuntimeException;
use Zend\Loader\AutoloaderFactory;
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
error_reporting(E_ALL | E_STRICT);
ini_set('memory_limit', '2048M');
chdir(__DIR__);
/**
* Sets up the MVC (application, service manager, autoloading) and the database.
*/
class Bootstrap
{
/** @var ServiceManager */
protected $serviceManager;
protected $applicationConfigPath;
/** @var EntityManager */
protected $entityManager;
public function __construct()
{
$this->applicationConfigPath = __DIR__ . '/../config/application.config.php';
}
/**
* Sets up the
*/
public function init()
{
// autoloading setup
static::initAutoloader();
// application configuration & setup
$applicationConfig = require_once $this->applicationConfigPath;
$this->prepareApplication($applicationConfig);
// database configuration & setup
$dbConfigs = $this->serviceManager->get('Config')['db'];
$this->setUpDatabase($dbConfigs);
// listeners & application bootstrap
$listeners = $this->prepareListeners();
$this->bootstrapApplication($listeners);
}
public function chroot()
{
$rootPath = dirname(static::findParentPath('module'));
chdir($rootPath);
}
protected function prepareApplication($config)
{
$serviceManagerConfig = isset($config['service_manager']) ? $config['service_manager'] : [];
$serviceManagerConfigObject = new ServiceManagerConfig($serviceManagerConfig);
$this->serviceManager = new ServiceManager();
$serviceManagerConfigObject->configureServiceManager($this->serviceManager);
$this->serviceManager->setService('ApplicationConfig', $config);
$this->serviceManager->get('ModuleManager')->loadModules();
}
protected function prepareListeners()
{
$listenersFromAppConfig = [];
$config = $this->serviceManager->get('config');
$listenersFromConfigService = isset($config['listeners']) ? $config['listeners'] : [];
$listeners = array_unique(array_merge($listenersFromConfigService, $listenersFromAppConfig));
return $listeners;
}
protected function bootstrapApplication($listeners)
{
$application = $this->serviceManager->get('Application');
$application->bootstrap($listeners);
}
protected function setUpDatabase(array $dbConfigs)
{
$databaseInitializer = new DatabaseInitializer($dbConfigs);
$databaseInitializer->setUp();
AbstractDbTest::setDbConfigs($dbConfigs);
AbstractControllerTest::setApplicationConfigPath($this->applicationConfigPath);
AbstractControllerTest::setDbConfigs($dbConfigs);
}
protected function initAutoloader()
{
$vendorPath = static::findParentPath('vendor');
if (file_exists($vendorPath.'/autoload.php')) {
include $vendorPath.'/autoload.php';
}
if (! class_exists('Zend\Loader\AutoloaderFactory')) {
throw new RuntimeException(
'Unable to load ZF2. Run `php composer.phar install`'
);
}
AutoloaderFactory::factory(array(
'Zend\Loader\StandardAutoloader' => array(
'autoregister_zf' => true,
'namespaces' => array(
__NAMESPACE__ => __DIR__,
),
),
));
}
protected function findParentPath($path)
{
$dir = __DIR__;
$previousDir = '.';
while (!is_dir($dir . '/' . $path)) {
$dir = dirname($dir);
if ($previousDir === $dir) {
return false;
}
$previousDir = $dir;
}
return $dir . '/' . $path;
}
}
$bootstrap = new Bootstrap();
$bootstrap->init();
$bootstrap->chroot();
引导
namespace Base\Test;
use Doctrine\ORM\EntityManager;
use PDO;
use PHPUnit\DbUnit\Database\DefaultConnection;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Sql;
use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
/**
* Class AbstractControllerTest
*
* @package Base\Test
*/
abstract class AbstractControllerTest extends AbstractHttpControllerTestCase
{
use DatabaseConnectionTrait;
/**
* @var string
*/
static private $applicationConfigPath;
/** @var Adapter */
protected $dbAdapter;
/** @var EntityManager */
protected $entityManager;
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
$this->setApplicationConfig(include self::$applicationConfigPath);
}
public static function setApplicationConfigPath(string $applicationConfigPath)
{
self::$applicationConfigPath = $applicationConfigPath;
}
protected function tearDown()
{
// Connections: 354
// Time: 5.7 minutes, Memory: 622.00MB
// OK (102 tests, 367 assertions)
// no optimization
// Connections: 326 (26 connections less)
// Time: 5.86 minutes, Memory: 620.00MB
// OK (102 tests, 367 assertions)
// if ($this->dbAdapter && $this->dbAdapter instanceof Adapter) {
// $this->dbAdapter->getDriver()->getConnection()->disconnect();
// }
// Connections: 354
// Time: 5.67 minutes, Memory: 620.00MB
// OK (102 tests, 367 assertions)
// $this->entityManager->close();
// Connections: 291 (63 connections less)
// Time: 5.63 minutes, Memory: 622.00MB
// OK (102 tests, 367 assertions)
// $this->entityManager->getConnection()->close();
// Connections: 264 (90 connections less)
// Time: 5.7 minutes, Memory: 620.00MB
// OK (102 tests, 367 assertions)
// if ($this->dbAdapter && $this->dbAdapter instanceof Adapter) {
// $this->dbAdapter->getDriver()->getConnection()->disconnect();
// }
// $this->entityManager->getConnection()->close();
// Connections: 251
// Time: 4.71 minutes, Memory: 574.00MB
// OK (102 tests, 367 assertions)
// After removing initialization of the EntityManager and the DbAdapter in the constructor and the setUp().
// closing DB connections
if ($this->dbAdapter && $this->dbAdapter instanceof Adapter) {
$this->dbAdapter->getDriver()->getConnection()->disconnect();
}
if ($this->entityManager && $this->entityManager instanceof EntityManager) {
$this->entityManager->getConnection()->close();
}
$reflectionObject = new \ReflectionObject($this);
foreach ($reflectionObject->getProperties() as $prop) {
if (!$prop->isStatic() && 0 !== strpos($prop->getDeclaringClass()->getName(), 'PHPUnit_')) {
$prop->setAccessible(true);
$prop->setValue($this, null);
}
}
$this->reset();
$this->application = null;
gc_collect_cycles();
unset($_SERVER['AUTH_USER']);
parent::tearDown();
}
protected function retrieveActualData($table, $idColumn, $idValue)
{
$sql = new Sql($this->getDbAdapter());
$select = $sql->select($table);
$select->where([$table . '.' . $idColumn . ' = ?' => $idValue]);
$statement = $sql->prepareStatementForSqlObject($select);
$result = $statement->execute();
$data = $result->current();
// Decreases the total number of the connections by 1 less.
// $this->dbAdapter->getDriver()->getConnection()->disconnect();
return $data;
}
protected function getEntityManager()
{
$this->entityManager = $this->entityManager
?: $this->getApplicationServiceLocator()->get('doctrine.entitymanager.orm_default')
;
return $this->entityManager;
}
protected function getDbAdapter()
{
$this->dbAdapter = $this->dbAdapter
?: $this->getApplicationServiceLocator()->get('Zend\Db\Adapter\Adapter')
;
return $this->dbAdapter;
}
}
namespace Base\Test;
use PDO;
use PHPUnit\DbUnit\Database\Connection;
use PHPUnit\DbUnit\Database\DefaultConnection;
use PHPUnit\DbUnit\InvalidArgumentException;
trait DatabaseConnectionTrait
{
/**
* @var array
*/
static private $dbConfigs;
/**
* @var PDO
*/
static private $pdo;
/**
* @var Connection
*/
private $connection;
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
}
/**
* @return Connection
*/
public function getConnection()
{
if (! $this->connection) {
if (! self::$dbConfigs) {
throw new InvalidArgumentException(
'Set the database configuration first.'
. ' '. 'Use the ' . self::class . '::setDbConfigs(...).'
);
}
if (! self::$pdo) {
self::$pdo = new PDO(
self::$dbConfigs['dsn'],
self::$dbConfigs['username'],
self::$dbConfigs['password'],
[PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'']
);
}
$this->connection = $this->createDefaultDBConnection(self::$pdo, self::$dbConfigs['database']);
}
return $this->connection;
}
public static function setDbConfigs(array $dbConfigs)
{
self::$dbConfigs = $dbConfigs;
}
/**
* Creates a new DefaultDatabaseConnection using the given PDO connection
* and database schema name.
*
* @see The original PHPUnit\DbUnit\TestCaseTrait#createDefaultDBConnection(...).
*
* @param PDO $connection
* @param string $schema
*
* @return DefaultConnection
*/
protected function createDefaultDBConnection(PDO $connection, $schema = '')
{
return new DefaultConnection($connection, $schema);
}
}
namespace Base\Test;
/**
* Class DatabaseInitializer
*
* @package Base\Test
*/
class DatabaseInitializer
{
use DatabaseConnectionTrait;
/**
* @var string
*/
private $database;
public function __construct(array $dbConfigs)
{
$this->database = $dbConfigs['database'];
self::$dbConfigs = $dbConfigs;
}
public function setUp()
{
$schemaSql = file_get_contents(self::$dbConfigs['scripts']['schema']);
$storedProceduresSql = file_get_contents(self::$dbConfigs['scripts']['stored-procedures']);
$basicDataSql = file_get_contents(self::$dbConfigs['scripts']['basic-data']);
$testDataSqlSet = array_map(function ($sqlFile) {
return file_get_contents($sqlFile);
}, self::$dbConfigs['scripts']['test-data']);
$this->dropDatabase();
$this->createDatabase();
$this->useDatabase();
$this->createSchema($schemaSql);
$this->createStoredProcedures($storedProceduresSql);
$this->createBasicData($basicDataSql);
$this->createTestData($testDataSqlSet);
}
public function tearDown()
{
self::$pdo = null;
}
protected function createDatabase()
{
$this->getDatabaseConnection()->exec('CREATE DATABASE IF NOT EXISTS ' . $this->database . ';');
}
protected function useDatabase()
{
$this->getDatabaseConnection()->exec('USE ' . $this->database . ';');
}
protected function createSchema(string $sql)
{
$this->getDatabaseConnection()->exec($sql);
}
protected function createBasicData(string $sql)
{
$this->getDatabaseConnection()->exec($sql);
}
protected function createTestData(array $sqlSet = [])
{
foreach ($sqlSet as $sql) {
$this->getDatabaseConnection()->exec($sql);
}
}
protected function createStoredProcedures(string $sql)
{
$statement = $this->getDatabaseConnection()->prepare($sql);
$statement->execute();
}
protected function dropDatabase()
{
$this->getDatabaseConnection()->exec('DROP DATABASE IF EXISTS ' . $this->database . ';');
}
protected function getDatabaseConnection()
{
return $this->getConnection()->getConnection();
}
}
namespace Test;
use Base\Test\AbstractControllerTest;
use Base\Test\AbstractDbTest;
use Base\Test\DatabaseInitializer;
use Doctrine\ORM\EntityManager;
use RuntimeException;
use Zend\Loader\AutoloaderFactory;
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
error_reporting(E_ALL | E_STRICT);
ini_set('memory_limit', '2048M');
chdir(__DIR__);
/**
* Sets up the MVC (application, service manager, autoloading) and the database.
*/
class Bootstrap
{
/** @var ServiceManager */
protected $serviceManager;
protected $applicationConfigPath;
/** @var EntityManager */
protected $entityManager;
public function __construct()
{
$this->applicationConfigPath = __DIR__ . '/../config/application.config.php';
}
/**
* Sets up the
*/
public function init()
{
// autoloading setup
static::initAutoloader();
// application configuration & setup
$applicationConfig = require_once $this->applicationConfigPath;
$this->prepareApplication($applicationConfig);
// database configuration & setup
$dbConfigs = $this->serviceManager->get('Config')['db'];
$this->setUpDatabase($dbConfigs);
// listeners & application bootstrap
$listeners = $this->prepareListeners();
$this->bootstrapApplication($listeners);
}
public function chroot()
{
$rootPath = dirname(static::findParentPath('module'));
chdir($rootPath);
}
protected function prepareApplication($config)
{
$serviceManagerConfig = isset($config['service_manager']) ? $config['service_manager'] : [];
$serviceManagerConfigObject = new ServiceManagerConfig($serviceManagerConfig);
$this->serviceManager = new ServiceManager();
$serviceManagerConfigObject->configureServiceManager($this->serviceManager);
$this->serviceManager->setService('ApplicationConfig', $config);
$this->serviceManager->get('ModuleManager')->loadModules();
}
protected function prepareListeners()
{
$listenersFromAppConfig = [];
$config = $this->serviceManager->get('config');
$listenersFromConfigService = isset($config['listeners']) ? $config['listeners'] : [];
$listeners = array_unique(array_merge($listenersFromConfigService, $listenersFromAppConfig));
return $listeners;
}
protected function bootstrapApplication($listeners)
{
$application = $this->serviceManager->get('Application');
$application->bootstrap($listeners);
}
protected function setUpDatabase(array $dbConfigs)
{
$databaseInitializer = new DatabaseInitializer($dbConfigs);
$databaseInitializer->setUp();
AbstractDbTest::setDbConfigs($dbConfigs);
AbstractControllerTest::setApplicationConfigPath($this->applicationConfigPath);
AbstractControllerTest::setDbConfigs($dbConfigs);
}
protected function initAutoloader()
{
$vendorPath = static::findParentPath('vendor');
if (file_exists($vendorPath.'/autoload.php')) {
include $vendorPath.'/autoload.php';
}
if (! class_exists('Zend\Loader\AutoloaderFactory')) {
throw new RuntimeException(
'Unable to load ZF2. Run `php composer.phar install`'
);
}
AutoloaderFactory::factory(array(
'Zend\Loader\StandardAutoloader' => array(
'autoregister_zf' => true,
'namespaces' => array(
__NAMESPACE__ => __DIR__,
),
),
));
}
protected function findParentPath($path)
{
$dir = __DIR__;
$previousDir = '.';
while (!is_dir($dir . '/' . $path)) {
$dir = dirname($dir);
if ($previousDir === $dir) {
return false;
}
$previousDir = $dir;
}
return $dir . '/' . $path;
}
}
$bootstrap = new Bootstrap();
$bootstrap->init();
$bootstrap->chroot();
因为我们看不到您的控制器、存储库等,所以我们不能说您“有错误”。通常,您应该在setUp
方法上建立数据库测试连接,并在tearDown
方法上销毁它。您不应该为应用程序启动连接。应用程序应在需要时启动该连接。既然你用的是教义,它就是这么做的。为什么在测试之前创建/调用EntityManager
如果只运行一个测试而不是整个测试用例,会发生什么?发生了多少连接?也许你在控制器上犯了个错误?也许在仓库里
这里是我看到的代码
- 我看不到初始化器在哪里使用。你在哪里使用它?我不喜欢那种方法
- 您的
DatabaseConnectionTrait
和AbstractControllerTest
类都有\u构造方法。如果一个类覆盖了trait中定义的方法,那么这个类的方法就起作用了。为什么有这种方法
- 你确定你没有反复做同样的事情吗?
getDatabaseConnection
,getConnection
,$this->getConnection()->getConnection()代码>
- 在哪里定义静态
$em
属性
unset
处理反射对象时将其取消设置。我知道这是一种简单快捷的方法,但是你应该考虑自己去做。这将是管理ram的更好方法
- 为什么要在
tearDown
方法中使用反射来破坏类的所有属性?破坏连接并让php管理垃圾李>
- 为什么要用
tearDown
方法销毁数据库?如果您将测试数据正确地提供给phpunit,则不需要它。检查DbUnit
的拆卸操作
我建议您一个接一个地运行测试,而不是整个测试用例并检查连接计数。您还应该使用DbUnit测试您的存储库,以确保它们正常工作。由于存储库负责数据库,可能连接数在它们身上增加。据我所知,每个测试都会实例化entity manager的新实例。你能在所有测试中重复使用它吗?谢谢你的评论!我添加了一个static
变量$em
并替换了行$this->entityManager=$this->getApplicationServiceLocator()->get('doctrine.entityManager.orm_default')在AbstractControllerTest
的构造函数中,通过以下方式进行编码:self::$em=self::$em?:$this->getApplicationServiceLocator()->get('doctrine.entitymanager.orm_default')$this->entityManager=self::$em
我还设置了一个调试断点,并检查了self::$em
是否实际被重用——是的,确实如此。但是这个更改并没有影响DB连接的数量。好的,但是应用程序代码仍然从ServiceLocator中获得一个新的EntityManager实例,我想,您是否也可以在locator中替换它?这就是我评论的目的——事实上,我应该提到(尽管我在一个基于Symfony的项目中这样做,但我不确定zend是否存在干净的方式……如果不是的话——尝试一些黑客方式)))我不确定,我是否正确理解了你。您的意思是跨(HTTP)请求使用相同的EntityManager实例?我认为,这是不可能的,因为ServiceLocator每次在应用程序引导时都会被实例化。好吧,我可以把整个地图藏起来