So this is my question. I have a legacy project with more than 700K PHP lines. Homemade framework.
Our ORM works, but I'd like to try the PhalconPHP in the project.
We are using Pimple as DI.
Is it possible to run just the Model/PHQL in a non Phalcon project?
If so, how?
Thanks
Yes, it is possible. Thanks to #calin in the PhalconPHP forum I found the answer. Basically the steps are the following ones:
Create an instance of the Phalcon DI (or any that extends the DI interface)
Set the modelsManager
Set the modelsMetadata
Set the db
Set the default DI
In the model, define the getSource if necessary.
Example:
<?php
$di = new \Phalcon\DI();
$di->set('modelsManager', function () {
return new \Phalcon\Mvc\Model\Manager();
});
$di->set('modelsMetadata', function () {
return new \Phalcon\Mvc\Model\MetaData\Memory();
});
$di->set('db', function () {
return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
'adapter' => 'Mysql',
'host' => 'xxx.dev',
'username' => 'xxxx',
'password' => 'yyy',
'dbname' => 'zzz',
));
});
\Phalcon\DI::setDefault($di);
That has to be called in some part before you will need the call to the Models/PHQL.
Define a normal Model, and call the models. It's pretty epic.
Model example:
<?php
namespace Vendor\App\Mvc\Model;
use \Phalcon\Mvc\Model;
class UserModel extends Model
{
public function getSource()
{
return "users";
}
}
And in some part of the code, after the calls to the Phalcon DI:
<?php
echo \Vendor\App\Mvc\Model\UserModel::find()->count();
Related
The title is confusing. Basically, I'm unable to access Laravel's utilities in classes that don't belong to the framework.
Error is: Call to a member function connection() on null in C:\xampp\htdocs\blazocket\vendor\laravel\framework\src\Illuminate\Databas e\Eloquent\Model.php:1571
I have a Websocket Server based on Ratchet library, it has the following strucutre:
namespace App\Http\Websockets;
$server = new \Ratchet\App('localhost', 8080);
$server->route('/api/socket', new WebSocketHandler, array('*'));
Snippet from WebSocketHandler:
namespace App\Http\Websockets;
use App\Models\Website;
class WebSocketHandler implements MessageComponentInterface {
protected $apis;
protected $clients;
public function __construct() {
$this->refreshDatabase();
$this->clients = new \SplObjectStorage;
}
private function refreshDatabase(){
$this->apis = Website::all();
}
}
I've tried doing this:
$app = require dirname( __FILE__ ) . '/../../../bootstrap/app.php';
$app->boot();
But It's not the solution (I tried it from a different SO issue). Also, I've read this, but I'm not sure about the way I could implement the "bootstraping" thing.
I'm aware that the error is happening because there's no connection between the class (WebSocketHandler) and the Laravel framework. I've tried every answer, and checking the docs, I couldn't find a way on my own to make this class connected.
Your help is greatly appreciated.
You have missed declaring the namespace. As you see here
there's a namespace declaration.
The obvious answer that you need to boot (Idk the correct term) Laravel and make it available for custom classes that have nothing to do with Laravel.
I do have this way for the database connection only. First, add these on the top of your class:
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Events\Dispatcher;
use Illuminate\Container\Container;
Then, create a new method in your class and call it once you initialize it.
private function setupLaravel(){
$capsule = new Capsule;
$capsule->addConnection([
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'mydatabase',
'username' => 'root',
'password' => "",
]);
$capsule->setEventDispatcher(new Dispatcher(new Container));
$capsule->setAsGlobal();
$capsule->bootEloquent();
}
If you're running Ratchet and want to access the DB via Eloquent ORM, this is the way.
I am currently working on a slim 3 application that is using a php-di bridge to serve dependencies. Everything is working flawlessly within controllers, however, I came across an issue when trying to add dependencies to a model observer. Eloquents capsule manager has its own container so I am unable to pass through dependencies (not injected) without it throwing an error.
Current setup:
$app = new \App\App; // extends \DI\Bridge\Slim\App
$container = $app->getContainer();
$capsule = new Capsule;
$capsule->addConnection($settings['settings']['db']);
$capsule->setEventDispatcher(new Dispatcher());
$capsule->setAsGlobal();
$capsule->bootEloquent();
// Sale model extends eloquent model
Sale::observe(new SaleObserver($container));
SaleObserver Class:
class SaleObserver {
protected $container;
public function __construct($container){
$this->container = $container;
}
public function saving(Sale $sale){
// Mail logic using container
}
}
This gives me the error triggered from my SaleObserver:
Unresolvable dependency resolving [Parameter #0 [ <required> $container ]]
I think the problem is that the SaleObserver is being resolved my eloquent's own container which is not allowing me to pass my PHP-DI "$container" through. Is something like this possible within slim 3 without a hacky approach?
I am testing with container just to see if I can pass something through, however, my main objective is to simply pass through my mail definition since it has all the configs already setup.
Update:
I have previously tried to type hint within the SaleObserver class like below in hopes that php-di would catch it with no avail.
use App\Mail\Contracts\MailerInterface;
class SaleObserver {
protected $mail;
public function __construct(MailerInterface $mail){
$this->mail = $mail;
}
public function updating(Sale $sale){
// Send Mail logic
}
}
I end up getting a different error that suggests that php-di auto wiring is not working with classes that are not not connected to a route even though auto wiring is set to true because the injected parameter is not automatically instantiating as it should.
This shows the error:
Uncaught ArgumentCountError: Too few arguments to function
The bridge I am using is PHP-DI/Slim-Bridge
Unfortunately, seems like Laravel's database event handling mechanism heavily relies on Laravel's container implementation. After reading this great post, I managed to make events work as expected.
I set up an instance of \Illuminate\Container\Container and added it to and used it to my application container ($container). This instance of Laravel container is going to be used only for handling events. Now I can set up an instance of \Illuminate\Events\Dispatcher and register it with setting up the Manager instance:
$container['laravel-container'] = function ($c){
return new \Illuminate\Container\Container;
};
$container['database-event-dispatcher'] = function ($c) {
return new \Illuminate\Events\Dispatcher($c['laravel-container']);
};
$capsule = new \Illuminate\Database\Capsule\Manager();
$capsule->addConnection($container['settings']['db']);
// Pass event dispatcher instance from application container
$capsule->setEventDispatcher($container['database-event-dispatcher']);
$capsule->setAsGlobal();
// Now that we're using events it is important to call $capsule->bootEloquent();
$capsule->bootEloquent();
A when creating a new instance of the observer, we can pass our applications $container, or only pass the objects we need in the observer.
So, assuming this is our SaleObserver class:
class SaleObserver {
protected $mail;
public function __construct(MailerInterface $mail){
$this->mail = $mail;
}
public function updating(Sale $sale){
// Send Mail logic
}
}
we should give it the mailer instance:
$container ['mailer'] = function ($c) {
// set up and return a MailerInterface
};
Sale::observe(new SaleObserver($container['mailer']));
It's possible to use just the Connection object with an empty Laravel container like this:
use Illuminate\Database\Connection;
use Illuminate\Database\Connectors\ConnectionFactory;
use Psr\Container\ContainerInterface as Container;
$container[Connection::class] = function (Container $container) {
$settings = $container->get('settings');
$config = [
'driver' => 'mysql',
'host' => $settings['db']['host'],
'database' => $settings['db']['database'],
'username' => $settings['db']['username'],
'password' => $settings['db']['password'],
'charset' => $settings['db']['charset'],
'collation' => $settings['db']['collation'],
'prefix' => '',
];
$factory = new ConnectionFactory(new \Illuminate\Container\Container());
$connection = $factory->make($config);
// Disable the query log to prevent memory issues
$connection->disableQueryLog();
return $connection;
};
I am migrating a project from Zend framework 1.4 to 2.4, I have a class in "vendor/custom/classes/User.php"
<?php
namespace Classes;
use Zend\Db\TableGateway\TableGateway;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Db\Adapter\Adapter;
class User
{
public function getItemById($id)
{
//$config = $this->getServiceLocator()->get('config');
//This only work in controller
$configs = array();
$adapter = new Adapter($configs);
$projectTable = new TableGateway('project', $adapter);
$rowset = $projectTable->select(array('type' => 'PHP'));
echo 'Projects of type PHP: ';
foreach ($rowset as $projectRow) {
echo $projectRow['name'] . PHP_EOL;
}
}
}
?>
I need to load merged configurations in my files in "config/autoload" , global.php and local.php. $config = $this->getServiceLocator()->get('config'); Can someone guide me how can I get these configurations from a custom class. Basically I am trying to do is writing set of classes like User, Project, Customer outside of Models and use them commonly in all modules like CMS, Admin Panel, Web site. Appreciate your guidance.
An approach could be using a factory.
You create a class UserFactory implementing Zend\ServiceManager\FactoryInterface. This class will have a method createService with a $serviceLocator parameter. You use this service locator to retrieve your dependencies and pass them to your User class.
In your User class you need to use a controller that accepts as parameters the dependencies that you need to pass to it
Since there is no direct way to access those configurations. I have wrote constants with DB access information in the local and global php files in config/autoload and used it in my class.
class DBManager
{
protected $adapter ;
protected $connection ;
/**
* Constructor
*/
public function __construct()
{
$configs = array(
'hostname' => DB_SERVER_NAME,
'driver' => 'Pdo_Mysql',
'database' => DB_DATABASE_NAME,
'username' => DB_USER_NAME,
'password' => DB_PASSWORD ,
);
$this->adapter = new Adapter($configs);
}
}
Currently I decided to look at Phalcon php as an alternate php framework to Codeigniter. I followed the tutorials on the website and it is pretty sweet the way it works. I'm still trying to wrap my head around few things though.
From my understanding, Models are bind with Database and mapped to the table in the database. I have a project, where I need to use 2 or more databases. The project has a backend (one database) and multiple frontends (another database). The easiest way is to run custom MySQL queries to fetch data from multiple databases. I'm not sure how to do that from the Model in the Phalcon. I looked over stackoverflow, tried few suggestions, but still no luck.
I would guess there should be some easy way to do so from the Model like $result=$this->query("SELECT * FROM backend.users")->fetch(); but it doesn't work.
Here what I have:
Controller:
class SignupController extends \Phalcon\Mvc\Controller{
function indexAction()
{
}
function registerAction()
{
$user = new Users();
$result=$user->saveNewUser();
print_r($result); // Nothing
//$result=$this->db->query("SELECT * FROM phalcon.system_users")->fetchAll();
//print_r($result); // Works
$this->view->disable();
}
}
Model:
class Users extends Phalcon\Mvc\Model
{
public function saveNewUser()
{
return $this->db; // how to run the query???
}
}
Bootstrap:
try {
//Register an autoloader
$loader = new \Phalcon\Loader();
$loader->registerDirs(array(
'../app/controllers/',
'../app/models/'
))->register();
//Create a DI
$di = new Phalcon\DI\FactoryDefault();
//Setup the database service
$di->set('db', function(){
return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => "localhost",
"username" => "root",
"password" => "123456",
"dbname" => ""
));
});
//Setup the view component
$di->set('view', function(){
$view = new \Phalcon\Mvc\View();
$view->setViewsDir('../app/views/');
return $view;
});
//Setup a base URI so that all generated URIs include the "tutorial" folder
$di->set('url', function(){
$url = new \Phalcon\Mvc\Url();
$url->setBaseUri('/phalcon/');
return $url;
});
//Handle the request
$application = new \Phalcon\Mvc\Application($di);
echo $application->handle()->getContent();
} catch(\Phalcon\Exception $e) {
echo "PhalconException: ", $e->getMessage();
}
I liked the way Codeigniter had it, not sure if Phalcon have a simple way of doing that. May be I need to load the extension or library to do that in the Model.
Thanks in advance!
Thanks jodator,
But it is a little bit different that I needed. I wanted to execute sql queries from the Model.
After spending more time testing and experimenting I think I figured it out. Just in case if someone has the same need to be able execute mysql queries from the model, here the way I figured it out. I'm not sure if impacts the performance, but it works.
Create a new Model class and call it for example BaseModel.php with the next inside:
class BaseModel extends Phalcon\Mvc\Model
{
public $db;
public function initialize()
{
$this->db=$this->getDi()->getShared('db');
}
}
The BaseModel will extend the Phalcon Model and I created a public property called $db. Then on the initialize() I used $this->getDi()->getShared('db') to grab shared dependency injector and assigned it to our $this->db. Now all Models that extend this BaseModel will have access to that property. Then in my Users Model I have next:
class Users extends BaseModel // Users extends out BaseModel and will have access to $db
{
public function test()
{
//print_r(\Phalcon\Di::getDefault()->getShared('db')); // This is the ugly way to grab the connection.
$result=$this->db->query("SELECT * FROM phalcon.system_users"); // Working now
echo $result->numRows();
print_r($result->fetchAll());
}
}
Now it works beautifully. I also figured out one more thing that might be interesting to someone who wants to use mysql queries (PDO) in Phalcon. I always use FETCH_ASSOC when fetching data, and to make life easier you can set up FETCH_ASSOC by default at the connection this way you don't need to setAttribute every time fetching data. Here is how I have done it. At the bootstrap, when setting DI class for the database connection, you can include the options....
//Setup the database service
$di->set('db', function(){
return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => "localhost",
"username" => "root",
"password" => "123456",
"dbname" => "",
'options' => [PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_PERSISTENT => TRUE,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC],
));
});
As you see the last option sets PDO::ATTR_DEFAULT_FETCH_MODE.
If anyone has better way to do that, please post it here.
I hope it will help newbies to Phalcon like me :)
You need to setup two database services in you config, like:
//Setup the database service
$di->set('db', function(){ /* like in what you have */ });
// then the other one
$di->set('dbBackend', function(){ /* like in what you have */ });
Then in your models change the db service
public function initialize()
{
parent::initialize();
$this->setConnectionService('dbBackend');
// or $this->setWriteConnectionService('dbB') and $this->setReadConnectionService('dbA')
}
Then just $model->create()
But if you want to run a query on different databases you can look at Pdo\Mysql Adapter.
Also models can have set table name $this->setSource() and schema $this->setSchema().
EDIT: a few weeks after I posted this question Evan Coury wrote an excellent blog post on the topic of the ZF2 ServiceManager, which is where I found the best answers to my questions: http://blog.evan.pro/introduction-to-the-zend-framework-2-servicemanager
--
I'm working on a project using ZendFramework 2.0.0beta4 and am having trouble using the Zend\ServiceManager to handle dependencies. Here is the current ZF2 ServiceManager documentation
It lists 6 sub-keys to use when registering classes with the ServiceManager for use in our modules: abstract_factories, aliases, factories, invokables, services, and shared. If I just want to register a model class which I'm going to use in my controller to pull data from a database, which one is best? I'm specifically trying to adapt an example from the ZF2 Skeleton Application shown below to my own application (DashboardTable is a model), and this example uses the factories way.
public function getServiceConfiguration()
{
return array(
'factories' => array(
'album-table' => function($sm) {
$dbAdapter = $sm->get('db-adapter');
$table = new DashboardTable($dbAdapter);
return $table;
},
'test-model' => Dashboard\Model\TestModel(),
),
);
}
However, I don't know how 'db-adapter' is getting into the ServiceManager ($sm) in my separate working example from the SkeletonApplication - it has to do with an entry in the autoloaded global.php config file which has a 'db' entry containing the DB info. Because I don't know exactly how that's getting from the config file to ServiceManager, I created the simple entry below that to reduce the problem to its base components - "test-model". When I comment out the 'dashboard-table' entry and call a function from TestModel in my controller which simply outputs some text. Below is the ServiceManager config from my Module.php
<?php
namespace Dashboard\Model;
class TestModel {
public function testMethod()
{
$testResult = "Hello";
return $testResult;
}
}
Which is then passed from my controller to the view:
<?php
namespace Dashboard\Controller;
use Zend\Mvc\Controller\ActionController;
use Zend\View\Model\ViewModel;
use Dashboard\Model\AlbumTable;
use Dashboard\Model\TestModel;
use Dashboard\Model\Dashboard;
class DashboardController extends ActionController
{
public function indexAction()
{
return new ViewModel(array(
'users' => $this->getTestModel()->testMethod(),
));
}
public function getAlbumTable()
{
if (!$this->albumTable) {
$sm = $this->getServiceLocator();
$this->albumTable = $sm->get('album-table');
}
return $this->albumTable;
}
public function getTestModel()
{
if (!$this->testModel) {
$sm = $this->getServiceLocator();
$this->testModel = $sm->get('test-model');
}
return $this->testModel;
}
}
This code gives me a completely blank page, no errors. When I comment out the ServiceManager config from Module.php and just render a new ViewModel without any passing any arguments in my DashboardController.php file the page renders normally - loading layout.phtml and index.phtml.
I believe I'm misunderstanding a fundamental piece of how to use the ServiceManager or possible ZF2 in general, and will greatly appreciate any insight anybody can give. This is also my first question on StackOverflow so I welcome any advice on formatting my question. Thanks.
There are two good options to get factories from service managers. One is the creation of factory classes, which happens most time in the Zend Framework code itself. The second one is using closures, as you are doing.
Make sure you do not type things like:
'test-model' => Dashboard\Model\TestModel(),
But a real closure like your first one is a good example. Secondly, the Service Manager always gives an exception when you try to get a service which fails to instantiate. Note this exception does not include the message why: the class might not be found or an exception is thrown during instantiation (for example because the service manager cannot instantiate a dependency of the service you are trying to get).
A last remark is you do not need to import FQCN (fully qualified class names) with use statements at the location you are trying to get. But you need to import the FQCNs when you are trying to instantiate.
So this works:
<?php
class MyClass
{
protected $sm;
public function setServiceManager($sm)
{
$this->sm = $sm;
}
public function doSomething()
{
$this->sm->get('some-special-key');
}
}
And this too:
<?php
use Foo\Bar\Baz;
$serviceConfig = array(
'factories' => array(
'some-special-key' => function($sm) {
return new Baz;
}
),
);
But this not (if you try to get a Foo\Bar\Baz):
<?php
$serviceConfig = array(
'factories' => array(
'some-special-key' => function($sm) {
return new Baz;
}
),
);
You might want to checkout my SlmCmfKernel repository. In my Module.php I include a service configuration file, which is put in a separate location. In another part of the code I get a service from the manager.
Just to clarify:
public function getServiceConfiguration()
{
return array(
'factories' => array(
'test-model' => function($sm){
return new Model\TestModel;
},
),
);
}
Can also be written as an invokable:
public function getServiceConfiguration()
{
return array(
'invokables' => array(
'test-model' => 'Model\TestModel',
),
);
}
In that case, you might want to consider having it defined in a config file instead of Module.php, as you'd be able to take advantage of config caching since it's simply an array of scalars.
I ended up finding the answer to my own question through more debugging (I previously hadn't had ini_set('display_errors', '1'); set - silly me).
The proper syntax to add a class to the ZF2 ServiceManager (within your Module.php) appears to be:
public function getServiceConfiguration()
{
return array(
'factories' => array(
'album-table' => function($sm) {
$dbAdapter = $sm->get('db-adapter');
$table = new AlbumTable($dbAdapter);
return $table;
},
'test-model' => function($sm){
return new Model\TestModel;
},
),
);
}
And just for completeness, in order to call a method from the class you're including you can use this in your controller file (DashboardController.php in my case) as long as you're extending the ActionController class:
class DashboardController extends ActionController
{
public function indexAction()
{
return new ViewModel(array(
'users' => $this->getTestModel()->testMethod(),
));
}
public function getTestModel()
{
if (!$this->testModel) {
$sm = $this->getServiceLocator();
$this->testModel = $sm->get('test-model');
}
return $this->testModel;
}
}
Where testMethod() is a method from within the TestModel class. Some notes from this for anybody new to Zend or namespaces - one of my issues was that I was using the full class name reference (Dashboard\Model\TestModel) when I had set the namespace at the top of the file to Dashboard, so the first Dashboard was unnecessary and caused PHP to look for Dashboard\Dashboard\Model\TestModel. Also, as of this writing sample ZF2 module are scarce - I recommend looking to samples like ZfcUser by EvanDotPro for examples on this type of thing.
My original confusion about the various sub-keys for adding classes to the ServiceManager still lingers though, so if you have any insight as to that I will continue to monitor this question and will mark your answer as "the" answer should you solve that bit, thank you :).