Loading base packages in xPDO - php

I once spent over an hour reverse engineering the xPDO constructor to figure out how to load base packages upon instantiation.
Unfortunately, I have lost that little snippet of code! And I'm left with this.
$this->database = new xPDO(
"mysql:host=" . $this->config->item('hostname', XPDO) .
";dbname=" . $this->config->item('database', XPDO),
$this->config->item('username', XPDO),
$this->config->item('password', XPDO),
$this->config->item('dbprefix', XPDO)
);
// This is line I would like to pass to the constructor.
$this->database->addPackage('packageName', $this->config->item('core_path') . "components/package/model/", '_packagePrefix');
I cannot find this anywhere in the documentation.
EDIT
With xPDO, you have to specifically add packages that are not loaded by default. And by default, xPDO doesn not load any packages on instantiation.
However, I did once spend a considerable amount of time, deconstructing the constructor of xPDO and found that there is an optional parameter that allows you to define an array of packages that will be loaded on instantiation.
My problem is that I cannot remember how to do this.

You can load base packages passing the right options to the xPDO constructor. This is the constructor definition:
$xpdo= new xPDO($dsn, $username= '', $password= '', $options= array(), $driverOptions= null)
The options array support many different configurations, the one you are looking for is xPDO::OPT_BASE_PACKAGES:
xPDO::OPT_BASE_PACKAGES — A comma-separated string of package
names/paths (separated by a colon) to be loaded upon instantiation.
Basically, you can do what you want modifying your code in this way:
$this->database = new xPDO(
"mysql:host=" . $this->config->item('hostname', XPDO) .
";dbname=" . $this->config->item('database', XPDO),
$this->config->item('username', XPDO),
$this->config->item('password', XPDO),
array(xPDO::OPT_BASE_PACKAGES => "package1:path1;prefix, package2:path2, ...")
);
Here's a link to the documentation where you can find further details about the options array: http://rtfm.modx.com/xpdo/2.x/getting-started/fundamentals/xpdo,-the-class/the-xpdo-constructor
EDIT
The format of the string is as follow:
"package_name:absolute_path;prefix"
prefix is optional. I have updated the code above with this format string.

While I'm not completely sure what you're asking for I suspect you are asking if there is some known method of passing an array of package names for packages with the same path.
Assuming this is correct I have the following possible solution (and if it's not then please, as scragar requested, clarify what you are asking):
Having looked # the file xpdo.class.php in the class xPDO & method addPackage() I see that it specifically verifies that the package name is a string & produces an error if it is not.
Therefore this method, addPackage() certainly does NOT allow an array to be passed to it.
However, I suspect you may be remembering that, if you use addPackage() on a package/extra directory that has multiple xPDO classes & mapping files & a schema... that ALL of the database tables represented will be able to be instantiated & utilized to perform CRUD operations on.
As an example I have two packages (from separate extras) that have the following paths core/components/[packageName]/ (where the thing in the brackets would be the actual name of the particular package).
In addition the model files are located in core/components/[packageName]/model/[packageName]/ as well as the subdirectory /mysql.
In BOTH cases when I add the package [packageName] all classes are available to be instantiated, since I have not included/required any of the files then addPackage() appears to be including them & therefore making them available for use as xPDO objects by using the modx method newQuery()
(so in code thats:
$modx->newQuery(nameOfxPDOClassToBeInstantiated);
)
(NOTE: all of the necessary classes for my two packages where generated from custom DB tables utilizing the very handy tool provided by Bob Ray here and explained by him in a straightforward tutorial here).
Hope that helps.
As an afterthought, here's the xPDO documentation on addPackage:
http://rtfm.modx.com/xpdo/2.x/class-reference/xpdo/xpdo.addpackage
And here's the documentation on utilizing the objects...
http://rtfm.modx.com/xpdo/2.x/getting-started/using-your-xpdo-model/retrieving-objects

Related

Add read replicas for SilverStripe website

I've managed to get a stable load balanced front end servers that can scale horizontally quite well however the next bottle neck would be the db. There was a blog post discussing scaling dbs horizontally however very little detail on it. I'm currently using PostgreSQL and so the only plugin I've found wouldn't work.
Are my only options creating my own HAProxy or rewriting the PostgreSQL plugin to allow connections with read replicas?
I'm using AWS for all my hosting
Firstly - I'd love to be corrected on this!
Having only had a quick look through some of the ORM classes in a SilverStripe 3.5 site, it looks like while the ORM does support multiple database connections (see DB::get_conn with argument for name) it is designed for specific use cases in mind. That is to say, you may have a module that needs to write to a specific database, so this would allow it to.
What you want is native and automatic support for this within the framework, so that all reads go to your slave(s) and writes go to your master. Unfortunately, it doesn't look like this comes out of the box. You might be able to achieve it by overloading a couple of the core SQL classes using the injector.
If you were to try it, this answer outlines how you could separate select statements out from the rest and run them through a different database connector.
As a quick example of how you might go at achieving this with SQLSelect, you will notice that it is injectable, which means you can easily overload it.
File: mysite/_config/injector.yml
Injector:
SQLSelect:
class: ReadOnlySQLSelect
You need to register a new database connection with the DB class:
File: mysite/_config.php
$readDatabaseConfig = array(/** define your DB credentials here, as with the default $databaseConfig **/);
if (!DB::connect($readDatabaseConfig, 'default_read')) {
user_error('Failed to connect to read replica DB!', E_USER_ERROR);
}
Now, overload the SQLSelect class and replace the parts of it that call the DB class methods. This class inherits from SQLExpression which is the class the contains the methods you actually care about in this instance:
File: mysite/code/ReadOnlySQLSelect.php
class ReadOnlySQLSelect extends SQLSelect
{
public function sql(&$parameters = array())
{
// Changed from SQLExpression: third parameter passed as connection name
$sql = DB::build_sql($this, $parameters, 'default_read');
if (empty($sql)) {
return null;
}
if ($this->replacementsOld) {
$sql = str_replace($this->replacementsOld, $this->replacementsNew, $sql);
}
return $sql;
}
public function execute()
{
$sql = $this->sql($parameters);
// Changed from SQLExpression: skip DB::prepared_query since it doesn't allow
// you to provide the connection name - replace it with its contents instead.
$conn = DB::get_conn('default_read');
return $conn->preparedQuery($sql, $parameters);
}
}
Note: SQLSelect::unlimitedRowCount should technically be replaced where it calls DB::prepared_query, since the prepared query method calls DB::get_conn with no arguments, so will always return the default connection. You could replace the DB::prepared_query line the same as used above:
$conn = DB::get_conn('default_read');
$result = $conn->preparedQuery($sql, $innerParameters);
If you implement the above method, also change new SQLSelect() to SQLSelect::create(), otherwise you'll end up with some queries that still hit the master server because it'll bypass your class by not using the injector.
There's also an instance in SQLConditionalExpression that you should replace too (::toSelect) but that is likely to affect query transformations from other child implementations of that class, and you won't be able to do much about it without either (A) PRing a fix to the framework or (B) overloading all the other SQL* classes.
At this point you should have everything you need to route select queries to your default_read connection.
Infrastructure
On the infrastructure side, you should be able to set up read replicas through the RDS console. When you do so it will provide you with a DNS endpoint for your replica node(s), which you can use in your _config.php to configure the connection to the read replica database.
If this works for you, you should create a module for it and put it up on GitHub - this would definitely be useful for others in future!
You may also consider making pull requests to the framework to add additional arguments to methods like DB::prepared_query to accept a connection name.
Also worth noting is that if you're using the mysqlnd database adapter you may be able to take advantage of read/write splitting, implemented with some sort of injector overloading but all handled at a lower level than the application layer.

Symfony 2 - how to parse %parameter% in my own Yaml file loader?

I have a Yaml loader that loads additional config items for a "profile" (where one application can use different profiles, e.g. for different local editions of the same site).
My loader is very simple:
# YamlProfileLoader.php
use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Yaml\Yaml;
class YamlProfileLoader extends FileLoader
{
public function load($resource, $type = null)
{
$configValues = Yaml::parse($resource);
return $configValues;
}
public function supports($resource, $type = null)
{
return is_string($resource) && 'yml' === pathinfo(
$resource,
PATHINFO_EXTENSION
);
}
}
The loader is used more or less like this (simplified a bit, because there is caching too):
$loaderResolver = new LoaderResolver(array(new YamlProfileLoader($locator)));
$delegatingLoader = new DelegatingLoader($loaderResolver);
foreach ($yamlProfileFiles as $yamlProfileFile) {
$profileName = basename($yamlProfileFile, '.yml');
$profiles[$profileName] = $delegatingLoader->load($yamlProfileFile);
}
So is the Yaml file it's parsing:
# profiles/germany.yml
locale: de_DE
hostname: %profiles.germany.host_name%
At the moment, the resulting array contains literally '%profiles.germany.host_name%' for the 'hostname' array key.
So, how can I parse the % parameters to get the actual parameter values?
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself. I could probably write my own parameter parser - get the parameters from the kernel, search for the %foo% strings and look-up/replace... but if there's a component ready to be used, I prefer to use this.
To give a bit more background, why I can't just include it into the main config.yml: I want to be able to load app/config/profiles/*.yml, where * is the profile name, and I am using my own Loader to accomplish this. If there's a way to wildcard import config files, then that might also work for me.
Note: currently using 2.4 but just about ready to upgrade to 2.5 if that helps.
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself.
Symfony's dependency injection component uses a compiler pass to resolve parameter references during the optimisation phase.
The Compiler gets the registered compiler passes from its PassConfig instance. This class configures a few compiler passes by default, which includes the ResolveParameterPlaceHoldersPass.
During container compilation, the ResolveParameterPlaceHoldersPass uses the Container's ParameterBag to resolve strings containing %parameters%. The compiler pass then sets that resolved value back into the container.
So, how can I parse the % parameters to get the actual parameter values?
You'd need access to the container in your ProfileLoader (or wherever you see fit). Using the container, you can recursively iterate over your parsed yaml config and pass values to the container's parameter bag to be resolved via the resolveValue() method.
Seems to me like perhaps a cleaner approach would be for you to implement this in your bundle configuration. That way your config will be validated against a defined structure, which can catch configuration errors early. See the docs on bundle configuration for more information (that link is for v2.7, but hopefully will apply to your version also).
I realise this is an old question, but I have spent quite a while figuring this out for my own projects, so I'm posting the answer here for future reference.
I tried a lot of options to resolve %parameter% to parameters.yml but no luck at all. All I can think of is parsing %parameter% and fetch it from container, no innovation yet.
On the other hand I don't have enough information about your environment to see the big picture but I just come up with another idea. It can be quite handy if you declare your profiles in your parameters.yml file and load it as an array in your controller or service via container.
app/config/parameters.yml
parameters:
profiles:
germany:
locale: de_DE
host_name: http://de.example.com
uk:
locale: en_EN
host_name: http://uk.example.com
turkey:
locale: tr_TR
host_name: http://tr.example.com
You can have all your profiles as an array in your controller.
<?php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
$profiles = $this->container->getParameter('profiles');
var_dump($profiles);
return $this->render('AcmeDemoBundle:Default:index.html.twig');
}
}
With this approach
you don't have to code a custom YamlLoader
you don't have to worry about importing parameters into other yml files
you can have your profiles as an array anytime you have the $container in your hand
you don't have to load/cache profile files one by one
you don't have to find a wildcard file loading solution
If I got your question correctly, this approach can help you.

Is it possible to add custom context to the .zfproject.xml?

I need to add custom context classesDirectory in applicationDirectory section to the .zfproject.xml.
I have created a context class, that extends Zend_Tool_Project_Context_Filesystem_Directory but the issue is that all contexts loading necessary to parse .zfproject.xml are hardcoded in Zend_Tool_Project_Provider_Abstract class as:
$contextRegistry->addContextsFromDirectory(
dirname(dirname(__FILE__)) . '/Context/Zf/', 'Zend_Tool_Project_Context_Zf_'
);
$contextRegistry->addContextsFromDirectory(
dirname(dirname(__FILE__)) . '/Context/Filesystem/', 'Zend_Tool_Project_Context_Filesystem_'
);
So I don't see any way to load my context without patching zend sources (or adding custom files to Zend library directories).
Is it even possible?
I don't believe it is possible without changing the ZF source code. You could try creating an issue on http://framework.zend.com/issues along with a patch. Please make this generic though.
Alternatively, what are you trying to do that requires a custom context? Maybe there's another way to solve it?

Developing/using a custom Resource Plugin in Zend Framework

We have used Zend_Log, which is configured in application.ini differently for different circumstances. We initialize it/get it in the bootstrap and store it in the registry:
$r = $this->getPluginResource('log');
$logger = $r->getLog();
But we've subclassed Zend_Log (say, Our_Log) to add customized features, and want to get it the same way. So then we have to make a new Resource Plugin. That seems quite easy - just copy Application/Resource/Log.php, rename the file to Ourlog.php, rename the class to class Zend_Application_Resource_Ourlog. For now, let's not worry about "Our_Log", the class -- just use the new Resource Plugin to get a Zend_Log, to reduce the variables.
So then, our new code in the bootstrap is:
$r = $this->getPluginResource('ourlog');
$logger = $r->getLog();
but of course this doesn't work, error applying method to non-object "r". According to the documentation,
"As long as you register the prefix path for this resource plugin, you
can then use it in your application."
but how do you register a prefix path? That would have been helpful. But that shouldn't matter, I used the same prefix path as the default, and I know the file is being read because I "require" it.
Anyway, any guidance on what simple step I'm missing would be greatly appreciated.
Thanks for the pointers -- so close, so close (I think). I thought I was getting it...
Okay, so I renamed the class Xyz_Resource_Xyzlog, I put it in library/Xyz/Resource/Xyzlog.php
Then, because I don't love ini files, in the bootstrap I put:
$loader=$this->getPluginLoader();
$loader->addPrefixPath('Xyz_Resource','Xyz/Resource/');
$r = $this->getPluginResource('xyzlog');
if (!is_object($r)) die ('Not an object!!');
Sudden Death. So, okay, do the ini:
pluginPaths.Xyz_Resource='Xyz/Resource/'
The same. No help. I believed that the basepaths of the plugin paths would include the PHP "include" paths. Am I mistaken in that? Any other ideas? I'll happily write up what finally works for me to help some other poor soul in this circumstance. Something to do with Name Spaces, maybe?
Plugin classes are resolved using the plugin loader, which works slightly differently to the autoloader; so just requiring the class in doesn't help you here. Instead, add this to your application.ini:
pluginPaths.Application_Resource = "Application/Resource"
you should then be able to use the class as normal. Since your path above will be checked before the default Zend one, you can also name your class 'Log' and still extend the Logger resource to override the standard functionality.

Doctrine 2 Classloader weird behaviour - wrong paths to classes

I'm having a bit of a problem with Classloader added to Doctrine 2 project.
I have simple directory structure like this:
config (bootstrap file)
html (docroot with templates/images/js etc)
php
Entities (doctrine 2 entities)
Responses (some transport objects)
Services (processing api and business logic - like session beans in java)
Each of the php subdirectories belongs to its own namespace (same as the name of directory).
I want to use aforementioned classloader to load these three different packages, so my bootstrap looks like this:
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine', $lib );
$classLoader->register();
$responsesCL = new \Doctrine\Common\ClassLoader('Responses', __DIR__.'/../php');
$responsesCL->register();
$entitiesCL = new \Doctrine\Common\ClassLoader('Entities', __DIR__.'/../php');
$entitiesCL->register();
$servicesCL = new \Doctrine\Common\ClassLoader('Services', __DIR__.'/../php');
$servicesCL->register();
Bold DIR is actually __ DIR __ php constant.
Now, I am referring in my services package to entities and this is where the problem starts, for some reason I get errors due to file not found problem, for example:
Failed opening required
'/var/www/projects/PlatformManagement/config/../php/Services/Entities/User.php'
(include_path='.:/usr/share/pear:/usr/share/php')
in
/usr/share/pear/Doctrine/Common/ClassLoader.php
on line 148
Somehow, there is extra 'Services' in the path, and obviously it's not valid path. I am a bit puzzled why that extra directory there? I tripple checked all namespaces, calls, and they are ok.
I need another pair of eyes to have look, I'm assuming I'm missing something obvious here, but can't spot it :|
Oh, this is latest Doctrine 2 Beta (4) and php 5.3.3 on fedora if that's of any help.
Thanks,
Greg
Is there any something like Doctrine\ORM\Tools\Setup::registerAutoloadPEAR()?
Doctrine's Autoloader may produce unexpected behaviors because it loads classes based on PHP include paths.
refer to this
In your case, Entities should have
namespace Entities;
declaration, not anything else.
And use entities like below,
use Entities\User;
new User;

Categories