Accessing Eloquent ORM inside Ratchet's MessageComponentInterface - php

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.

Related

Is it possible to use PHP-DI definitions instead of Eloquents Capsule Managers Built in IoC within Slim 3?

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;
};

What is the proper syntax to get a db instance?

I use eloquent outside laravel.
Here is the file responsible of it:
class DbSql
{
public function db()
{
$capsule = new Capsule;
$capsule->addConnection( [
'driver' => Settings::DATABASE_DRIVER,
'host' => Settings::DATABASE_HOST,
'database' => Settings::DATABASE_NAME,
'username' => Settings::DATABASE_USERNAME,
'password' => Settings::DATABASE_PASSWORD,
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => ''
] );
# Make this Capsule instance available globally
$capsule->setAsGlobal();
return $capsule;
}
}
In controllers, I instantiate the instance once, and may use it several time in a controller:
$dbInstance = ( new DbSql() )->db(); // Create the instance
// Instance usages:
$images = $dbInstance->table( 'images_attachments' )->where...
$files = $dbInstance->table( 'files' )->where...
.....
Should I instantiate with:
$dbInstance = ( new DbSql() )->db(); as in the code above
or with:
$dbInstance = ( new DbSql() )->db()->getConnection();
what's the proper way ?
First of all, there is no need to wrap the Capsule (which is a Illuminate\Database\Capsule\Manager instance) in another class to make easier to retrieve it. In fact, the Illuminate\Database\Capsule\Manager provides a singleton instance and simple accessors.
Once you call
$capsule->setAsGlobal();
the Capsule instance will be accessible statically from everywhere via Capsule::.
This because the setAsGlobal() method, under the hood, does simply this:
static::$instance = $this;
Saving the instance in a static class variable to retrieve it on subsequent calls to Capsule::.
So, once you call setAsGlobal(), it is possible to do, for example:
Capsule::select('select * from user');
and you'll get the results of the query on the previously created connection.
However, you can see that the select method doesn't exists on the Manager (alias Capsule) class.
Why does it works?
Because the Manager class overrides the __callStatic() php magic method
class Manager
{
use CapsuleManagerTrait;
// ...
public static function __callStatic($method, $parameters)
{
return static::connection()->$method(...$parameters);
}
}
So the call to select() method is redirected to the underlying \Illuminate\Database\Connection instance automagically.
This means that:
there is no need to wrap the Manager/Capsule in an external class, since it is already a wrapper over the Connection class. No need to add another wrapper.
If you prefer to wrap it, however, I suggest you to returning the Manager/Capsule instance and not the underlying connection, since the suggested approach in the docs is to work directly with the Manager

Zend Framework 2 Access DB from Custom Library

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);
}
}

Is possible to use PHQL in a non PhalconPHP project?

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();

PHPUnit: How to require my apps config file

I have a file called application.config.php in my apps root directory. I want to require it or autoload it for my tests. The config file is like so:
<?php
// database connection
$config = array(
'database' => array(
'dsn' => 'mysql:host=localhost;dbname=budgetz',
'user' => 'budgetz_user',
'password' => 't1nth3p4rk',
),
);
My app uses these to connect to the database. So, to test my models they also need to connect to the database, .. or some database. Is it just a matter of something along the lines of requiring it in the test file:
<?php
require_once 'vendor/autoload.php';
require_once 'application.config.php';
class MapperTest extends PHPUnit_Framework_TestCase {
public function testFetchOne() {
$dbAdapter = new DatabaseAdapter($config['database']);
$userMapper = new UserMapper($dbAdapter); // using UserMapper but any child of Mapper will do
$user = $userMapper->fetchOne(1);
$this->assertsEquals(1, $user->id, 'message');
}
}
I tried this but I get the error:
There was 1 error:
1) MapperTest::testFetchOne
Undefined variable: config
/var/www/new_orm/test/MapperTest.php:8
What am I doing wrong? Also, I appreciate anyone giving some advise on best practise here. Perhaps this approach to requiring a config file in every page is a little old. Thanks
Globals is an option, but not good one.
Create your class, witch extends PHPUnit_Framework_TestCase. Then use the setup to set your config.
e.g.
class myTestCase extends PHPUnit_Framework_TestCase {
private $config;
public function setUp() {
$this->config = ....
}
public function getConfig() {
return $this->configl
}
Then your testcases should extends myTestCase. You could access config with
$this->getConfig();
Anyway, accessing dev db is not a good idea, maybe it is better to mock the work with the db ?
Try
public function testFetchOne() {
global $config;

Categories