I have entity SourceElanceProfileImport, and I create this entity in action and set some data and when I flush have error:
Catchable Fatal Error: Object of class Proxies\__CG__\Artel\ProfileBundle\Entity\Teams could not be converted to string
and question, why if I find teams like this I have Proxies object
$team = $em->getRepository('ArtelProfileBundle:Teams')->findOneById($id);
I tra hard code write id and have nor entity Teams
$id = '2';
$team = $em->getRepository('ArtelProfileBundle:Teams')->findOneById($id);
// $team entity Teams, not Proxies
what’s happened not right in this action?
action:
public function elanceProfileAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$url = $request->get('url');
$email = $request->get('email');
$firstName = $request->get('firstName');
$user = $em->getRepository('ArtelProfileBundle:Users')->getCompanyByEmail($request->get('email'));
$id = $user[0]->getTeams()->getId();
$team = $em->getRepository('ArtelProfileBundle:Teams')->findOneById($id);
if (!empty($user)) {
$elance_import = new SourceElanceProfileImport();
$hepler = $this->container->get('artel.profile.additional_function');
$pass = $hepler->generatePassword();
$elance_import
->setEmail($email)
->setSecurityHash(sha1($pass))
->setElanceUrl($url)
->setTeamId($team)
;
$em->persist($elance_import);
$em->flush();
and entity:
class SourceElanceProfileImport
{
/**
* #var \Teams
*
* #ORM\ManyToOne(targetEntity="Teams")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="team_id", referencedColumnName="id", onDelete="CASCADE")
* })
*/
private $teamId;
/**
* #var integer
*
* #ORM\Column(name="team_id", type="integer")
*/
public $team;
when I add __toString to entity Teams I have:
public function __toString()
{
return $this->company;
}
Error in one or more bulk request actions:
index: /aog/sourceelanceprofileimport/5 caused MapperParsingException[failed to parse [team_id]]; nested: NumberFormatException[For input string: "Gutkowski LLC"];
why in another entity work fine, what’s wrong I don’t know ((
update
I solved but I think this is solved not fine and that’s one I don’t delete this question
I add in User entity:
private function _load()
{
// lazy loading code
}
/**
* Get teams
*
* #return \Artel\ProfileBundle\Entity\Teams
*/
public function getLoadTeams()
{
$this->_load();
return parent::getId();
}
and in my action
$user = $em->getRepository('ArtelProfileBundle:Users')->getCompanyByEmail($request->get('email'));
$id = $user[0]->getLoadTeams();
$team = $em->getRepository('ArtelProfileBundle:Teams')->findOneById($id);
than I have object Teams in variable $team, BUT when I flush I still have error:
Catchable Fatal Error: Object of class Artel\ProfileBundle\Entity\Teams could not be converted to string
I solved, but problem it was in persist entity and in config fos_elastic
I still have Proxies Teams entity but in action
$user = $em->getRepository('ArtelProfileBundle:Users')->getCompanyByEmail($request->get('email'));
$id = $user[0]->getTeams()->getId();
$team = $em->getRepository('ArtelProfileBundle:Teams')->findOneById($id);
if (!empty($user)) {
$elance_import = new SourceElanceProfileImport();
$hepler = $this->container->get('artel.profile.additional_function');
$pass = $hepler->generatePassword();
$elance_import
->setEmail($email)
->setSecurityHash(sha1($pass))
->setElanceUrl($url);
$em->persist($elance_import);
$elance_import->setTeamId($team);
$em->flush();
and flush fine entity create and have id Teams (Proxies object Teams)
and teamId this is relationship with entity Teams and in fos_elastica need write like this:
sourceelanceprofileimport:
mappings:
id:
type: integer
elance_url:
type: string
full_name:
type: string
status:
type: string
created:
type: date
approved_at:
type: date
imported_at:
type: date
team_id:
type: "nested"
properties:
id: ~
persistence:
# the driver can be orm, mongodb or propel
# listener and finder are not supported by
# propel and should be removed
driver: orm
model: Artel\ProfileBundle\Entity\SourceElanceProfileImport
provider: ~
listener: ~
finder: ~
and I have entity in my DB and Elastic DB
First, you will have proxies every time an entity is not completely populated. Proxies are used to return partial entities.
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/partial-objects.html
Looking your code I noticed this:
$team = $em->getRepository('ArtelProfileBundle:Teams')->findOneById($id);
But you can do the same in this way:
$team = $em->find('ArtelProfileBundle:Teams', $id); // EntityManager::find has 2 arguments: entity and id.
Underneath you do this:
$user = $em->getRepository('ArtelProfileBundle:Users')->getCompanyByEmail($request->get('email'));
Did you implement some custom code on getCompanyByEmail? If not, you can use Doctrine's methods:
$user = $em->getRepository('ArtelProfileBundle:Users')->findOneByEmail($request->get('email'));
Those are magic methods: findOneBy or findBy. One returns a single object or NULL, the other one returns an array with results.
You can also do this:
$user = $em->getRepository('ArtelProfileBundle:Users')->findOneBy(['email' => $request->get('email')]); // you can add more key/value filters on the array
Now just to be sure, did you implement APC as bytecode cache? Because when I make changes on my entities I usually need to flush APC (restarting the webserver) to force the cache to regenerate it. If misterious errors occur maybe APC should be restarted.
Now, regarding the flush error, did you implement a __toString method on any entity? Probably it's not returning a string.
Related
I am adding a feature to a Symfony 2.7 application. I have a controller that has been defined as a service, and it in turn takes in another service. Here are the relevant contents from my services.yml file:
app.service.video_derivative:
class: MyCompany\AppBundle\Service\VideoDerivativeService
arguments:
- "#app.repository.video_derivative"
- "#doctrine.orm.default_entity_manager"
api.controller.video_derivative:
class: MyCompany\AppBundle\Controller\VideoDerivativeController
arguments:
- "#app.service.video_derivative"
... and the relevant code in my controller looks like this:
public function __construct(VideoDerivativeServiceInterface $videoDerivativeService)
{
$this->videoDerivativeService = $videoDerivativeService;
}
/**
* #param $id
* #return JsonResponse
*
* #Route("/admin/video-derivative/create-by-clip-id/{id}", name="create_clip_by_id")
*/
public function byClipIdAction($id)
{
$responseArray = [
'foo' => 'bar',
'baz' => 'qux',
];
return new JsonResponse($responseArray);
}
... but when I pull up my controller in a browser, I get the following message:
Catchable Fatal Error: Argument 1 passed to
MyCompany\AppBundle\Controller\VideoDerivativeController::__construct()
must be an instance of
MyCompany\AppBundle\Service\VideoDerivativeServiceInterface, none given,
called in /usr/src/app/app/cache/dev/classes.php on line 2200 and
defined
... so it looks like I'm doing something wrong. How would you go about debugging this?
It turned out that what I needed was this annotation over the class:
/**
* #\Sensio\Bundle\FrameworkExtraBundle\Configuration\Route(service="api.controller.video_derivative")
*/
Once that was in place, I got a much more helpful error and was able to move on with development.
In a symfony projects, I'm trying to persist a line of an association table (profil_role) composed of two objects (profil and role).
First, I developed The create action in the ProfilRoleController of the second project this way:
/** #var Roles $role */
$em = $this->getDoctrine()->getManager('main_project');
$role = $em->getRepository("MyBundle\Entity\Roles")->find($roleId);
$profil = $em->getRepository("MyBundle\Entity\Profil")->find($profilId);
$profilRole = new ProfilRoles();
$profilRole->setRoleId($role->getId());
$profilRole->setProfilId($profil->getId());
$em->persist($profilRole);
$em->flush();
This part of code, call then the post entity action present in the main project:
/**
* #Rest\View(statusCode=Response::HTTP_CREATED)
* #Rest\Post("/profil_roles")
*/
public function postEntityAction(ProfilRoles $profilRole)
{
$em = $this->getDoctrine()->getManager();
$em->persist($profilRole);
$em->flush();
return $profilRole;
}
When I try to execute my code i'm getting this king of error:
Execution failed for request: POST /api/profil_roles? HTTP/1.1 {"profil":{"id":"12"},"role":{"id":"3"}}: HTTPCode 500, body {"code":500,"message":"Unable to guess how to get a Doctrine instance from the request information."}
I've tried to use the #ParamConverter annotation, but I don't how to use it my case.
try this:
public function postEntityAction() {
$postData = $request->request->all();
$profileRole = $postData['profile_role']
Instead of this:
public function postEntityAction(ProfilRoles $profilRole)
#AlessandroMinoccheri I've tried to be inspired by your reply to do this and i'ts workin, i don't know if it's the correct way.
/**
* #param ProfilRoles $profilRole
* #param Request $request
* #return ProfilRoles
* #Rest\View(statusCode=Response::HTTP_CREATED)
* #Rest\Post("/profil_roles")
*/
public function postEntityAction(Request $request)
{
$profilRole = new ProfilRoles();
$em = $this->getDoctrine()->getManager();
$requete = $request->request->all();
$profilRole->setProfilId($requete['profil']['id']);
$profilRole->setRoleId($requete['role']['id']);
$em->persist($profilRole);
$em->flush();
return $profilRole;
}
I'm starting with FOSRestBundle and when I get the values of an entity without relations and display it in the browser, I have no problem. But when I try to get an entity with relations, it shows me an error with code: 500.
Here is the code:
app/config/config.yml:
fos_rest:
routing_loader:
default_format: json
param_fetcher_listener: true
body_listener: true
format_listener: true
view:
view_response_listener: 'force'
ApiRestBundle/Controller/UserController (this works fine)
/**
* #return array
* #Rest\Get("/users")
* #Rest\View()
*/
public function getUsersAction()
{
$response = array();
$em = $this->getDoctrine()->getManager();
$users = $em->getRepository('CASUsuariosBundle:User')->findAll();
$view = $this->view($users);
return $this->handleView($view);
}
APIRestBunde/Controller/CategoryController (this doesn't works)
/**
* #return array
* #Rest\Get("/categories")
* #Rest\View()
*/
public function getCategoriesAction()
{
$response = array();
$em = $this->getDoctrine()->getManager();
$categories = $em->getRepository('CASEventBundle:Category')->findAll();
$view = $this->view($categories);
return $this->handleView($view);
}
the error code:
{"error":{"code":500,"message":"Internal Server
Error","exception":[{"message":"Notice: Undefined index:
name","class":"Symfony\Component\Debug\Exception\ContextErrorException","trace":[{"namespace":"","short_class":"","class":"","type":"","function":"","file":"C:\xampp\htdocs\CASPruebas\vendor\doctrine\orm\lib\Doctrine\ORM\Persisters\BasicEntityPersister.php","line":1758,"args":[]}...
your problem is a little bit complicated to solve.
This error code can mean a lot of different things!
But I think it is not directly a FOSRestBundle problem. Maybe you have a relation problem on your Category entity...
What is the result of this: doctrine:schema:validate
Edit : Maybe it will be more simple to solve it if you give us the full error code.
I currently ran into the problem of handling the authentification of a user on the server, using Laravel and RachetPHP.
What I tried so far:
I changed the driver type of the session to database, giving me an id and payload column. Using \Session::getId() returns a 40 character string.
The cookie information, sent by the WebSocket-Connection does contain a XSRF-TOKEN and a laravel_session, both containing > 200 characters string. The database ID of the users session differs from the id, returned by \Session::getId().
I am already sending the current CSRF-token via the websocket message, but I have no clue how to verify it (the built-in verifier uses requests - which I don't have in the websocket server scope).
Generic Use case:
A User posts a comment in thread. The payload of the sent object would then be:
Something to verify the user (an ID or a token).
The comment itself
If you were to send the user ID, anyone could temper the packet and send the message under another ones user.
My use case:
A user can have n-characters. A character has an avatar, an id, a name, etc.
The user is only used to:
authenticate at the server.
access his characters, and thus perform basic CRUD operations on his characters.
I also have a table locations - a "virtual place", a character can be in... so I got a one-to-one relationship between character and location. The user (character) can then send messages in a location via websocket. The message is inserted at the database on the server. At this point, I need to know:
If the user is authenticated (csrf-token ?)
If the user is the owner of the character (it's very simple to spoof the request with another user's character id)
If you need more information, please let me know.
So this is how I solved this a while ago. In my example, I'm working with Socket.IO, but I'm pretty sure you can easily rewrite the Socket.IO part to get it to work with RachetPHP as well.
Socket Server
The socket server depends on the files cookie.js and array.js, and the node modules express, http, socket.io, request and dotenv. I'm not the original author of cookie.js, but there is no author mentioned in the comments, so I'm not able to give any credits for this, sorry.
This is the server.js file which starts the server. It is a simple socket server that tracks who is currently online. The interesting part however is when the server makes a POST request to socket/auth on the Laravel application:
var express = require('express');
var app = express();
var server = require('http').Server(app)
var io = require('socket.io')(server);
var request = require('request');
var co = require('./cookie.js');
var array = require('./array.js');
// This loads the Laravel .env file
require('dotenv').config({path: '../.env'});
server.listen(process.env.SOCKET_SERVER_PORT);
var activeSockets = {};
var disconnectTimeouts = {};
// When a client connects
io.on('connection', function(socket)
{
console.log('Client connected...');
// Read the laravel_session cookie.
var cookieManager = new co.cookie(socket.handshake.headers.cookie);
var sess = cookieManager.get("laravel_session"); // Rename "laravel_session" to whatever you called it
// This is where the socket asks the Laravel app to authenticate the user
request.post('http://' + process.env.SOCKET_SERVER_HOST + '/socket/auth?s=' + sess, function(error, response, body)
{
try {
// Parse the response from the server
body = JSON.parse(body);
}
catch(e)
{
console.log('Error while parsing JSON', e);
error = true;
}
if ( ! error && response.statusCode == 200 && body.authenticated)
{
// Assign users ID to the socket
socket.userId = body.user.id;
if ( ! array.contains(activeSockets, socket.userId))
{
// The client is now 'active'
activeSockets.push(socket.userId);
var message = body.user.firstname + ' is now online!';
console.log(message);
// Tell everyone that the user has joined
socket.broadcast.emit('userJoined', socket.userId);
}
else if (array.hasKey(disconnectTimeouts, 'user_' + socket.userId))
{
clearTimeout(disconnectTimeouts['user_' + socket.userId]);
delete disconnectTimeouts['user_id' + socket.userId];
}
socket.on('disconnect', function()
{
// The client is 'inactive' if he doesn't reastablish the connection within 10 seconds
// For a 'who is online' list, this timeout ensures that the client does not disappear and reappear on each page reload
disconnectTimeouts['user_' + socket.userId] = setTimeout(function()
{
delete disconnectTimeouts['user_' + socket.userId];
array.remove(activeSockets, socket.userId);
var message = body.user.firstname + ' is now offline.';
console.log(message);
socket.broadcast.emit('userLeft', socket.userId);
}, 10000);
});
}
});
});
I added some comments to the code, so it should be pretty self-explanatory. Please note that I added SOCKET_SERVER_HOST and SOCKET_SERVER_PORT to my Laravel .env-file in order to be able to change the host and port without editing the code and run the server on different environments.
SOCKET_SERVER_HOST = localhost
SOCKET_SERVER_PORT = 1337
Authenticating a user by a session cookie with Laravel
This is the SocketController which parses the cookie and responds whether the user could be authenticated or not (JSON response). Its the same mechanism that you described in your answer. It's not the best design to handle the cookie parsing in the controller, but it should be OK in this case, because the controller only handles that one thing and its functionality isn't used at another point in the application.
/app/Http/Controllers/SocketController.php
<?php namespace App\Http\Controllers;
use App\Http\Requests;
use App\Users\UserRepositoryInterface;
use Illuminate\Auth\Guard;
use Illuminate\Database\DatabaseManager;
use Illuminate\Encryption\Encrypter;
use Illuminate\Http\Request;
use Illuminate\Routing\ResponseFactory;
/**
* Class SocketController
* #package App\Http\Controllers
*/
class SocketController extends Controller {
/**
* #var Encrypter
*/
private $encrypter;
/**
* #var DatabaseManager
*/
private $database;
/**
* #var UserRepositoryInterface
*/
private $users;
/**
* Initialize a new SocketController instance.
*
* #param Encrypter $encrypter
* #param DatabaseManager $database
* #param UserRepositoryInterface $users
*/
public function __construct(Encrypter $encrypter, DatabaseManager $database, UserRepositoryInterface $users)
{
parent::__construct();
$this->middleware('internal');
$this->encrypter = $encrypter;
$this->database = $database;
$this->users = $users;
}
/**
* Authorize a user from node.js socket server.
*
* #param Request $request
* #param ResponseFactory $response
* #param Guard $auth
* #return \Symfony\Component\HttpFoundation\Response
*/
public function authenticate(Request $request, ResponseFactory $response, Guard $auth)
{
try
{
$payload = $this->getPayload($request->get('s'));
} catch (\Exception $e)
{
return $response->json([
'authenticated' => false,
'message' => $e->getMessage()
]);
}
$user = $this->users->find($payload->{$auth->getName()});
return $response->json([
'authenticated' => true,
'user' => $user->toArray()
]);
}
/**
* Get session payload from encrypted laravel session.
*
* #param $session
* #return object
* #throws \Exception
*/
private function getPayload($session)
{
$sessionId = $this->encrypter->decrypt($session);
$sessionEntry = $this->getSession($sessionId);
$payload = base64_decode($sessionEntry->payload);
return (object) unserialize($payload);
}
/**
* Fetches base64 encoded session string from the database.
*
* #param $sessionId
* #return mixed
* #throws \Exception
*/
private function getSession($sessionId)
{
$sessionEntry = $this->database->connection()
->table('sessions')->select('*')->whereId($sessionId)->first();
if (is_null($sessionEntry))
{
throw new \Exception('The session could not be found. [Session ID: ' . $sessionId . ']');
}
return $sessionEntry;
}
}
In the constructor you can see that I refer to the internal middleware. I added this middleware to only allow the socket server to make requests to socket/auth.
This is what the middleware looks like:
/app/Http/Middleware/InternalMiddleware.php
<?php namespace App\Http\Middleware;
use Closure;
use Illuminate\Routing\ResponseFactory;
class InternalMiddleware {
/**
* #var ResponseFactory
*/
private $response;
/**
* #param ResponseFactory $response
*/
public function __construct(ResponseFactory $response)
{
$this->response = $response;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (preg_match(env('INTERNAL_MIDDLEWARE_IP'), $request->ip()))
{
return $next($request);
}
return $this->response->make('Unauthorized', 401);
}
}
To get this middleware to work, register it in the Kernel and add the INTERNAL_MIDDLEWARE_IP property - that is just a regular expression defining which IP addresses are allowed - to your .env-file:
Local testing (any IP):
INTERNAL_MIDDLEWARE_IP = /^.*$/
Production env:
INTERNAL_MIDDLEWARE_IP = /^192\.168\.0\.1$/
I'm sorry I could not help you out with RachetPHP, but I think you get a good idea how this can be solved.
I think I found a solution. Although not very clean, it does what it's supposed to do (I guess...)
The WebSocket-Server gets started by an Artisan Command (by mmochetti#github). I inject these classes into the Command:
Illuminate\Contracts\Encryption\Encrypter
App\Contracts\CsrfTokenVerifier - a custom CsrfTokenVerifier, that simply compares 2 strings (going to put more of the follow logic code in there)
I pass these instances from the command to the server. On the onMessage method, I parse the message sent, containing:
The CSRF-Token of the user
The character-id of the user
I then check if the token is valid, and if the user is the owner of the character.
public function onMessage(ConnectionInterface $from, NetworkMessage $message) {
if (!$this->verifyCsrfToken($from, $message)) {
throw new TokenMismatchException;
}
if (!$this->verifyUser($from, $message)) {
throw new \Exception('test');
}
...
}
private function verifyUser(ConnectionInterface $conn, NetworkMessage $message) {
$cookies = $conn->WebSocket->request->getCookies();
$laravel_session = rawurldecode($cookies['laravel_session']);
$id = $this->encrypter->decrypt($laravel_session);
$session = Session::find($id);
$payload = unserialize(base64_decode($session->payload));
$user_id = $payload['user_id'];
$user = User::find($user_id);
$characters = $this->characterService->allFrom($user);
$character_id = $message->getHeader()['character_id'];
return $characters->contains($character_id);
}
private function verifyCsrfToken($from, NetworkMessage $message) {
$header = $this->getHeaderToken($from);
return $this->verifier->tokensMatch($header, $message->getId());
}
The code could be cleaner for sure, but as a quick hack, it works. I think, instead of using a model for the Session, I should use the Laravel DatabaseSessionHandler.
For Laravel > 5 i use this code:
$cookies = $conn->WebSocket->request->getCookies();
$laravel_session = rawurldecode($cookies['laravel_session']);
$id = $this->encrypter->decrypt($laravel_session);
if(Config::get('session.driver', 'file') == 'file')
{
$session = File::get(storage_path('framework/sessions/' . $id));
}
$session = array_values(unserialize($session));
return $session[4]; // todo: Hack, please think another solution
To get cookies from client through websocket you must change domain in session config and change everywhere websocket host to your domain:
'domain' => 'your.domain.com',
I'm have an authenticated section on my app, but the authentication is done via oauth to a 3rd party service. I get the 200 callback from the service, now I create/find my user and set him as logged in.
So my provider is:
providers:
users:
entity: { class: MainBundle:User, property: id }
My user implements the security's UserInterface, although I don't have the username, password, etc properties on my user entity. I assume the id is then the only identifier I can use then.
My token is set as follows:
$token = new UsernamePasswordToken($user, null, 'secured_area', $user->getRoles());
$this->securityContext->setToken($token);
I wish to do this without using JMS bundle for now; my serialization looks like this:
/**
* Serialize
* #return string|void
*/
public function serialize()
{
return serialize(array(
'id' => $this->getId(),
'display_name' => $this->getDisplayName(),
'email' => $this->getEmail(),
));
}
/**
* Unserialize
* #return mixed|void
*/
public function unserialize($data)
{
$data = unserialize($data);
$this->setId($data['id']);
$this->setDisplayName($data['display_name']);
$this->setEmail($data['email']);
return $this;
}
Using the above I get an infinite loop redirect.
At first I only serialized the id, but then all the other properties of the user isn't available.
Then I tried serializing the whole object ($this), but that gives me a xdebug nesting level error of 1000.
I'm a bit lost of how to make this authentication work with serialization
security.yml
providers:
users:
entity: { class: MainBundle:User, property: id }
login logic
$token = new UsernamePasswordToken($user, null, 'secured_area', $user->getRoles());
$this->securityContext->setToken($token);
I took out the serialization out of the user entity, also not implementing equatable interface. Also still do not have the interface properties, although this is needed:
public function getUsername()
{
return $this->getId();
}