Symfony 3.2 ODM Doctrine array of MongoId's - mongodb

How to make array of MongoId's in Symfony 3.2 Doctrine ODM serialized/deserialized properly ?
Document
namespace Acme\StoreBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document(collection="Voter")
*/
class Voter
{
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\Collection
*/
protected $inlist;
/**
* Get id
*
* #return id $id
*/
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* Set inlist
*
* #param collection $inlist
* #return $this
*/
public function setInlist($inlist)
{
$this->inlist = $inlist;
return $this;
}
/**
* Get inlist
*
* #return collection $inlist
*/
public function getInlist()
{
return $this->inlist;
}
}
Controller:
/**
* #Route("/voter/{id}")
* #Method({"GET"})
*/
public function getAction($id)
{
$product = $this->get('doctrine_mongodb')
->getRepository('AcmeStoreBundle:Voter')
->find($id);
if (!$product) {
throw $this->createNotFoundException('No product found for id ' . $id);
}
$serializer = $this->get('serializer');
$data = $serializer->serialize(
$product,
'json'
);
$response = new JsonResponse();
$response->setContent($data);
return $response;
}
Serialized Json:
{
"id": "593e99a8de6c84f5ecec3094",
"inlist": [
{
"timestamp": 1417718686,
"pID": 3335,
"inc": 9127278,
"$id": "5480ab9e282e26070d8b456e"
},
{
"timestamp": 1417718686,
"pID": 3335,
"inc": 9127273,
"$id": "5480ab9e282e26070d8b4569"
},
{
"timestamp": 1417718686,
"pID": 3335,
"inc": 9127272,
"$id": "5480ab9e282e26070d8b4568"
},
{
"timestamp": 1417718686,
"pID": 3335,
"inc": 9127275,
"$id": "5480ab9e282e26070d8b456b"
},
{
"timestamp": 1417718686,
"pID": 3335,
"inc": 9127274,
"$id": "5480ab9e282e26070d8b456a"
},
{
"timestamp": 1411754988,
"pID": 2674,
"inc": 9127271,
"$id": "5425abec8f3723720a8b4567"
}
]
}
I want it to be serialized to be an array of (string) id's, and deserialize it back to array of MongoId's:
{
"id": "593e99a8de6c84f5ecec3094",
"inlist": ['5425abec8f3723720a8b4567', '5480ab9e282e26070d8b456b' ...]
}

Since you mentioned "right way" in the comments here's how I would do it:
class VoterAPIRepresentation
{
public $id;
public $inlist = [];
public function __construct(Voter $voter)
{
$this->id = (string) $voter->id;
foreach ($voter->inlist as $i) {
$this->inlist[] = (string) $i['$id'];
}
}
}
Above class is responsible for data representation in API since entity itself shouldn't be concerned with that. Then in controller:
public function getAction($id)
{
$product = $this->get('doctrine_mongodb')
->getRepository('AcmeStoreBundle:Voter')
->find($id);
if (!$product) {
throw $this->createNotFoundException('No product found for id ' . $id);
}
$serializer = $this->get('serializer');
$data = $serializer->serialize(
new VoterAPIRepresentation($product),
'json'
);
$response = new JsonResponse();
$response->setContent($data);
return $response;
}
Pro of this approach is that if you change the entity you don't need to be concerned with any endpoints and data they're returning since these two beings are not connected. On the other hand writing such classes is quite boring BUT it pays off for complex objects and representations.

setter/getter way worked for me even with serializer / deserializer
/**
* Set actions
*
* #param collection $actions
* #return $this
*/
public function setActions($actions)
{
$re = [];
foreach ($actions as $action) {
$action['cid'] = new \MongoId($action['cid']);
$re[] = $action;
}
$this->actions = $re;
return $this;
}
/**
* Get actions
*
* #return collection $actions
*/
public function getActions()
{
$re = [];
foreach ($this->actions as $action) {
$action['cid'] = (string)$action['cid'];
$re[] = $action;
}
return $re;
}

Related

The controller must return a response (Object(AppBundle\\Entity\\User) given)

I got this message "The controller must return a response (Object(App Bundle\Entity\User) given)." when i try to PATCH with postman a user password or username ... please help
BUT the username or password change ... ! i don't know why but that's working just the message is not good ...
My Userscontroller :
use AppBundle\Entity\EntityMerger;
use AppBundle\Entity\User;
use AppBundle\Exception\ResourceValidationException;
use Lexik\Bundle\JWTAuthenticationBundle\Encoder\JWTEncoderInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use FOS\RestBundle\Controller\Annotations as Rest;
/**
* #Security("is_anonymous() or is_authenticated()")
*/
class UsersController extends AbstractController
{
/**
* #var UserPasswordEncoderInterface
*/
private $passwordEncoder;
/**
* #var JWTEncoderInterface
*/
private $jwtEncoder;
/**
* #var EntityMerger
*/
private $entityMerger;
/**
* UsersController constructor.
* #param UserPasswordEncoderInterface $passwordEncoder
* #param JWTEncoderInterface $jwtEncoder
* #param EntityMerger $entityMerger
*/
public function __construct(UserPasswordEncoderInterface $passwordEncoder, JWTEncoderInterface $jwtEncoder, EntityMerger $entityMerger)
{
$this->passwordEncoder = $passwordEncoder;
$this->jwtEncoder = $jwtEncoder;
$this->entityMerger = $entityMerger;
}
/**
* #Rest\View()
* #Security("is_granted('show', theUser)", message="Access denied")
*/
public function getUserAction(?User $theUser)
{
if (null === $theUser) {
throw new NotFoundHttpException();
}
return $theUser;
}
/**
*
* #Rest\Post(
* path = "/users",
* name = "users_add"
* )
* #Rest\View(StatusCode=201)
* #ParamConverter(
* "user",
* converter="fos_rest.request_body",
* options={"deserializationContent"={"groups"={"Deserialize"}}}
* )
*/
public function postUserAction(User $user,
ConstraintViolationListInterface $violations)
{
if (count($violations) > 0) {
$message = 'The user is not valid: ';
foreach ($violations as $violation) {
$message .= sprintf(
"Field %s: %s ",
$violation->getPropertyPath(),
$violation->getMessage()
);
}
throw new ResourceValidationException($message);
}
$this->encodePassword($user);
$user->setRoles([User::ROLE_USER]);
$this->persistUser($user);
return $user;
}
/**
* #Rest\Patch(
* path = "/users/{theUser}",
* name= "patch_user"
* )
* #ParamConverter(
* "modifiedUser",
* converter="fos_rest.request_body",
* options={
* "validator"={"groups"={"Patch"}},
* "deserializationContext"={"groups"={"Deserialize"}}
* }
* )
* #Security("is_granted('edit', theUser)", message="Access Denied")
*/
public function patchUserAction(?User $theUser, User $modifiedUser,
ConstraintViolationListInterface $violations)
{
if (null === $theUser) {
throw new NotFoundHttpException();
}
if (empty($modifiedUser->getPassword())) {
$modifiedUser->setPassword(null);
}
if (count($violations) > 0) {
$message = 'The user is not valid: ';
foreach ($violations as $violation) {
$message .= sprintf(
"Field %s: %s",
$violation->getPropertyPath(),
$violation->getMessage()
);
}
throw new ResourceValidationException($message);
}
$this->entityMerger->merge($theUser, $modifiedUser);
$this->encodePassword($theUser);
$this->persistUser($theUser);
return $theUser;
}
/**
* #param User $user
*/
protected function encodePassword(User $user): void
{
$user->setPassword(
$this->passwordEncoder->encodePassword(
$user,
$user->getPassword()
)
);
}
/**
* #param User $user
*/
protected function persistUser(User $user): void
{
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
}
}
My EntityMerger:
namespace AppBundle\Entity;
use Doctrine\Common\Annotations\Reader as AnnotationReader;
use Doctrine\ORM\Mapping\Id;
class EntityMerger
{
/**
* #var AnnotationReader
*/
private $annotationReader;
public function __construct(AnnotationReader $annotationReader)
{
$this->annotationReader = $annotationReader;
}
/**
* #param $entity
* #param $changes
*/
public function merge($entity, $changes)
{
$entityClassName = get_class($entity);
if (false === $entityClassName) {
throw new \InvalidArgumentException('$entity is not a class');
}
$changesClassName = get_class($changes);
if (false == $changesClassName) {
throw new \InvalidArgumentException('$entity is not a class');
}
if (!is_a($changes, $entityClassName)) {
throw new \InvalidArgumentException("Cannot merge object of class $changesClassName with object of class $entityClassName");
}
$entityReflection = new \ReflectionObject($entity);
$changesReflection = new \ReflectionObject($changes);
foreach ($changesReflection->getProperties() as $changedProperty) {
$changedProperty->setAccessible(true);
$changedPropertyValue = $changedProperty->getValue($changes);
if(null === $changedPropertyValue) {
continue;
}
if (!$entityReflection->hasProperty($changedProperty->getName())) {
continue;
}
$entityProperty = $entityReflection->getProperty($changedProperty->getName());
$annotation = $this->annotationReader->getPropertyAnnotation($entityProperty, Id::class);
if (null !== $annotation) {
continue;
}
$entityProperty->setAccessible(true);
$entityProperty->setValue($entity, $changedPropertyValue);
}
}
}
The message correctly states that you are not returning a response object.
From the manual, The only requirement for a controller is to return a Response object
If you want to return an object you could return JSON:
return $this->json(['user' => $theUser]);
This is just a guess from your code, because your question does not state what you want to return.

Too many Redirects with Controller

When I try to define my routes with an ImagesController, I get a "site redirected you too many times" error when going to what should be the index. I'm stumped. Am I over-tired and not seeing something obvious?
web.php
Route::prefix('images')->group(function(){
Route::post('add-tag', 'ImagesController#addTag');
Route::get('test', 'ImagesController#index');
});
When I access /images/test, the controller responds correctly. When I have it defined as '' or / it redirects too much. I'm not setting them simultaneously.
Route::prefix('images')->group(function(){
Route::post('add-tag', 'ImagesController#addTag');
// like this:
Route::get('', 'ImagesController#index');
// or like the following:
Route::get('/', 'ImagesController#index');
});
I've even tried to define /images before the prefix group to no avail. images/test is fine in this case.
Route::get('images', 'ImagesController#index');
Route::prefix('images')->group(function(){
Route::post('add-tag', 'ImagesController#addTag');
Route::get('test', 'ImagesController#index');
});
ImagesController.php
namespace App\Http\Controllers;
use \Auth;
use App\Helpers;
use App\Image;
use App\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Console\Helper\Helper;
class ImagesController extends Controller
{
private $sortBy = [
'created_at',
'updated_at',
'orig_name',
'orig_size'
];
private $defaultSortBy = 'created_at';
private $defaultOrder = 'desc';
/**
* #param Request $request
* #return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index(Request $request)
{
$sort = $request->get('sort', $this->defaultSortBy);
$sort = in_array($sort, $this->sortBy) ? $sort : 'created_at';
$order = $request->get('order', $this->defaultOrder);
$order = $order == 'desc' ? 'desc' : 'asc';
$images = Image::orderBy($sort, $order)
->whereQuestionable(0)
->wherePrivate(0)
->paginate(config('image.images_per_page'));
if ($images !== null && $sort !== $this->defaultSortBy) {
$images->appends(['sort' => $sort]);
}
if ($images !== null && $order !== $this->defaultOrder) {
$images->appends(['order' => $order]);
}
return view(
'images.index',
[
'images' => $images,
'sort' => $sort,
'order' => $order
]
);
}
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param int $id
* #return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
//this function works correctly, but I suppose it may be interfering?
public function addTag(Request $request)
{
Log::debug(print_r($request->all(), true));
$error = '';
$uid = trim($request->input('uid', ''));
$newTag = Helpers::prepTag($request->input('tag', ''));
if (!Auth::check()){
$error .= 'You can only add tags to images if you are logged in.';
} else if ($uid === '' || $newTag === '') {
$error .= 'UID and tag cannot be empty';
} else {
$image = Image::whereUid($uid)->unQuestionable()->first();
$currentTags = [];
if (count($image->tags)){
foreach ($image->tags as $currentTag) {
$currentTags[$currentTag->id] = $currentTag->tag;
}
}
if ($image === null) {
$error .= "Image $uid was not found.";
} else {
$tag = Tag::firstOrNew(['tag'=>$newTag]);
if ($tag->ip == '') {
$tag->ip = $request->getClientIp();
$tag->user_id = Auth::check() ? Auth::user()->id : 5;
$tag->save();
}
//\Log::info(print_r($currentTags, true));
//\Log::info(print_r($tag, true));
$addTag = !(bool)isset($currentTags[$tag->id]);
$image->tags()->sync([$tag->id], false);
}
}
if ($request->ajax()) {
if ($error !== '') {
return response()->json(['message' => $error],400);
}
return response()->json(['tagId' => $tag->id, 'imageUid' => $image->uid, 'tag' => $tag->tag, 'isAdmin' => true, 'addTag' => $addTag]);
} else {
if ($error !== '') {
Helpers::alert(3, $error, 'Tag Not Added');
}
return redirect('/');
}
}
}
As I have a model named Images implicit binding was interfering with my defined routes for a controller with the name ImagesController, and applying the prefix /images. After changing the name in the routing to image this issue went away.

MimeMailParser put message part together

i'm building a small interface that pipe messages from postfix via stdin, and want to remove attachments from email/ anyway, i don't want to remove all the attachments, just those who are too big to be emailed.
i found MimeMailParser as a good point of start and made a few modifications to it's code (made some private methods public, in order to call them seperatley). here is the class in my version:
<?php
require_once('attachment.class.php');
/**
* Fast Mime Mail parser Class using PHP's MailParse Extension
* #author gabe#fijiwebdesign.com
* #url http://www.fijiwebdesign.com/
* #license http://creativecommons.org/licenses/by-sa/3.0/us/
* #version $Id$
*/
class MimeMailParser {
/**
* PHP MimeParser Resource ID
*/
public $resource;
/**
* A file pointer to email
*/
public $stream;
/**
* A text of an email
*/
public $data;
/**
* Stream Resources for Attachments
*/
public $attachment_streams;
public $parts;
/**
* Inialize some stuff
* #return
*/
public function __construct() {
$this->attachment_streams = array();
}
/**
* Free the held resouces
* #return void
*/
public function __destruct() {
// clear the email file resource
if (is_resource($this->stream)) {
fclose($this->stream);
}
// clear the MailParse resource
if (is_resource($this->resource)) {
mailparse_msg_free($this->resource);
}
// remove attachment resources
foreach($this->attachment_streams as $stream) {
fclose($stream);
}
}
/**
* Set the file path we use to get the email text
* #return Object MimeMailParser Instance
* #param $mail_path Object
*/
public function setPath($path) {
// should parse message incrementally from file
$this->resource = mailparse_msg_parse_file($path);
$this->stream = fopen($path, 'r');
$this->parse();
return $this;
}
/**
* Set the Stream resource we use to get the email text
* #return Object MimeMailParser Instance
* #param $stream Resource
*/
public function setStream($stream) {
// streams have to be cached to file first
if (get_resource_type($stream) == 'stream') {
$tmp_fp = tmpfile();
if ($tmp_fp) {
while(!feof($stream)) {
fwrite($tmp_fp, fread($stream, 2028));
}
fseek($tmp_fp, 0);
$this->stream =& $tmp_fp;
} else {
throw new Exception('Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.');
return false;
}
fclose($stream);
} else {
$this->stream = $stream;
}
$this->resource = mailparse_msg_create();
// parses the message incrementally low memory usage but slower
while(!feof($this->stream)) {
mailparse_msg_parse($this->resource, fread($this->stream, 2082));
}
$this->parse();
return $this;
}
/**
* Set the email text
* #return Object MimeMailParser Instance
* #param $data String
*/
public function setText($data) {
$this->resource = mailparse_msg_create();
// does not parse incrementally, fast memory hog might explode
mailparse_msg_parse($this->resource, $data);
$this->data = $data;
$this->parse();
return $this;
}
/**
* Parse the Message into parts
* #return void
* #private
*/
private function parse() {
$structure = mailparse_msg_get_structure($this->resource);
$this->parts = array();
foreach($structure as $part_id) {
$part = mailparse_msg_get_part($this->resource, $part_id);
$this->parts[$part_id] = mailparse_msg_get_part_data($part);
}
}
/**
* Retrieve the Email Headers
* #return Array
*/
public function getHeaders() {
if (isset($this->parts[1])) {
return $this->getPartHeaders($this->parts[1]);
} else {
throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.');
}
return false;
}
/**
* Retrieve the raw Email Headers
* #return string
*/
public function getHeadersRaw() {
if (isset($this->parts[1])) {
return $this->getPartHeaderRaw($this->parts[1]);
} else {
throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.');
}
return false;
}
/**
* Retrieve a specific Email Header
* #return String
* #param $name String Header name
*/
public function getHeader($name) {
if (isset($this->parts[1])) {
$headers = $this->getPartHeaders($this->parts[1]);
if (isset($headers[$name])) {
return $headers[$name];
}
} else {
throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.');
}
return false;
}
/**
* Returns the email message body in the specified format
* #return Mixed String Body or False if not found
* #param $type Object[optional]
*/
public function getMessageBody($type = 'text') {
$body = false;
$mime_types = array(
'text'=> 'text/plain',
'html'=> 'text/html'
);
if (in_array($type, array_keys($mime_types))) {
foreach($this->parts as $part) {
if ($this->getPartContentType($part) == $mime_types[$type]) {
$headers = $this->getPartHeaders($part);
$body = $this->getPartBody($part);
break;
}
}
} else {
throw new Exception('Invalid type specified for MimeMailParser::getMessageBody. "type" can either be text or html.');
}
return $body;
}
/**
* get the headers for the message body part.
* #return Array
* #param $type Object[optional]
*/
public function getMessageBodyHeaders($type = 'text') {
$headers = false;
$mime_types = array(
'text'=> 'text/plain',
'html'=> 'text/html'
);
if (in_array($type, array_keys($mime_types))) {
foreach($this->parts as $part) {
if ($this->getPartContentType($part) == $mime_types[$type]) {
$headers = $this->getPartHeaders($part);
break;
}
}
} else {
throw new Exception('Invalid type specified for MimeMailParser::getMessageBody. "type" can either be text or html.');
}
return $headers;
}
/**
* Returns inline content files.
* #return Array
*/
public function getInlineContent() {
$content = array();
foreach($this->parts as $part) {
$content_id = $this->getPartContentId($part);
if ($content_id !== FALSE) {
$content[] = new MimeMailParser_attachment(
$this->getPartContentName($part),
$this->getPartContentType($part),
$this->getAttachmentStream($part),
$this->getPartContentDisposition($part),
$this->getPartHeaders($part)
);
}
}
return $content;
}
/**
* Returns the attachments contents in order of appearance
* #return Array
* #param $type Object[optional]
*/
public function getAttachments() {
$attachments = array();
$dispositions = array("attachment","inline");
foreach($this->parts as $part) {
$disposition = $this->getPartContentDisposition($part);
if (in_array($disposition, $dispositions) === TRUE) {
if (isset($part['disposition-filename']) === FALSE) {
$part['disposition-filename'] = md5(uniqid());
}
$attachments[] = new MimeMailParser_attachment(
$part['disposition-filename'],
$this->getPartContentType($part),
$this->getAttachmentStream($part),
$disposition,
$this->getPartHeaders($part)
);
}
}
return $attachments;
}
/**
* Return the Headers for a MIME part
* #return Array
* #param $part Array
*/
private function getPartHeaders($part) {
if (isset($part['headers'])) {
return $part['headers'];
}
return false;
}
/**
* Return a Specific Header for a MIME part
* #return Array
* #param $part Array
* #param $header String Header Name
*/
private function getPartHeader($part, $header) {
if (isset($part['headers'][$header])) {
return $part['headers'][$header];
}
return false;
}
/**
* Return the ContentType of the MIME part
* #return String
* #param $part Array
*/
private function getPartContentType($part) {
if (isset($part['content-type'])) {
return $part['content-type'];
}
return false;
}
/**
* Return the ContentName of the MIME part
* #return String
* #param $part Array
*/
private function getPartContentName($part) {
if (isset($part['content-name'])) {
return $part['content-name'];
}
return false;
}
/**
* Return the Content Disposition
* #return String
* #param $part Array
*/
private function getPartContentDisposition($part) {
if (isset($part['content-disposition'])) {
return $part['content-disposition'];
}
return false;
}
/**
* Return the Content id
* #return String
* #param $part Array
*/
private function getPartContentId($part) {
if (isset($part['content-id'])) {
return $part['content-id'];
}
return false;
}
/**
* Retrieve the raw Header of a MIME part
* #return String
* #param $part Object
*/
private function getPartHeaderRaw(&$part) {
$header = '';
if ($this->stream) {
$header = $this->getPartHeaderFromFile($part);
} else if ($this->data) {
$header = $this->getPartHeaderFromText($part);
} else {
throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email parts.');
}
return $header;
}
/**
* Retrieve the Body of a MIME part
* #return String
* #param $part Object
*/
private function getPartBody(&$part) {
$body = '';
if ($this->stream) {
$body = $this->getPartBodyFromFile($part);
} else if ($this->data) {
$body = $this->getPartBodyFromText($part);
} else {
throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email parts.');
}
return $body;
}
/**
* Retrieve the Header from a MIME part from file
* #return String Mime Header Part
* #param $part Array
*/
private function getPartHeaderFromFile(&$part) {
$start = $part['starting-pos'];
$end = $part['starting-pos-body'];
fseek($this->stream, $start, SEEK_SET);
$header = fread($this->stream, $end-$start);
return $header;
}
/**
* Retrieve the Body from a MIME part from file
* #return String Mime Body Part
* #param $part Array
*/
private function getPartBodyFromFile(&$part) {
$start = $part['starting-pos-body'];
$end = $part['ending-pos-body'];
fseek($this->stream, $start, SEEK_SET);
$body = fread($this->stream, $end-$start);
return $body;
}
/**
* Retrieve the Header from a MIME part from text
* #return String Mime Header Part
* #param $part Array
*/
public function getPartHeaderFromText(&$part) {
$start = $part['starting-pos'];
$end = $part['starting-pos-body'];
$header = substr($this->data, $start, $end-$start);
return $header;
}
/**
* Retrieve the Body from a MIME part from text
* #return String Mime Body Part
* #param $part Array
*/
public function getPartBodyFromText(&$part) {
$start = $part['starting-pos-body'];
$end = $part['ending-pos-body'];
$body = substr($this->data, $start, $end-$start);
return $body;
}
/**
* Read the attachment Body and save temporary file resource
* #return String Mime Body Part
* #param $part Array
*/
private function getAttachmentStream(&$part) {
$temp_fp = tmpfile();
array_key_exists('content-transfer-encoding', $part['headers']) ? $encoding = $part['headers']['content-transfer-encoding'] : $encoding = '';
if ($temp_fp) {
if ($this->stream) {
$start = $part['starting-pos-body'];
$end = $part['ending-pos-body'];
fseek($this->stream, $start, SEEK_SET);
$len = $end-$start;
$written = 0;
$write = 2028;
$body = '';
while($written < $len) {
if (($written+$write < $len )) {
$write = $len - $written;
}
$part = fread($this->stream, $write);
fwrite($temp_fp, $this->decode($part, $encoding));
$written += $write;
}
} else if ($this->data) {
$attachment = $this->decode($this->getPartBodyFromText($part), $encoding);
fwrite($temp_fp, $attachment, strlen($attachment));
}
fseek($temp_fp, 0, SEEK_SET);
} else {
throw new Exception('Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.');
return false;
}
return $temp_fp;
}
/**
* Decode the string depending on encoding type.
* #return String the decoded string.
* #param $encodedString The string in its original encoded state.
* #param $encodingType The encoding type from the Content-Transfer-Encoding header of the part.
*/
public function decode($encodedString, $encodingType) {
if (strtolower($encodingType) == 'base64') {
return base64_decode($encodedString);
} else if (strtolower($encodingType) == 'quoted-printable') {
return quoted_printable_decode($encodedString);
} else {
return $encodedString;
}
}
}
?>
i call the code and use it as follows:
<?php
ini_set("display_errors","on");
error_reporting(E_ALL);
require_once('MimeMailParser.class.php');
$Parser = new MimeMailParser();
$Parser->setText(file_get_contents('php://stdin'));
$attachments = $Parser->getAttachments();
/*foreach($attachments as $attachment) {
echo $attachment->filename;
}*/
echo $Parser->getHeadersRaw(); # print email header
foreach ($Parser->parts as $xPart=>$yPart) {
if ($xPart=='1') {
continue;
}
echo $Parser->getPartHeaderFromText($yPart);
echo $Parser->getPartBodyFromText($yPart);
}
?>
that sends the message and all of the conetnts appears ok - beside one issue - attachments are not displayed. although, when i click "show original" the attachments as base64 encoded does appear on the message source.
any idea?

PHPUnit Zend_Test_PHPUnit_DatabaseTestCase fails to truncate table

I am setting up some db integration testing using Zend_Test_PHPUnit_DatabaseTestCase.
My tests run but the db tables do not get truncated so an add test fails assertion - as the xml I provide as a dataset does not match the db can anybody suggest why
TestCase
class ArtworkDBTest extends DatabaseTestCase
{
public function testAddArtwork()
{
$data=array("artwork_name"=>'test',"description"=>'test',"imgpath"=>'test',"size"=>'test',"price_information"=>'test',"category"=>1,"artwork_order"=>1);
$mockedLog=$this->getMock("Log",array("log"));
$artwork = new shop_Artwork($mockedLog,Zend_Db_Table_Abstract::getDefaultAdapter());
$artwork->addArtwork($data);
$this->assertDataSetsMatchXML('artwork-add.xml', $dataSet);
}
}
Database testing setup code (adapted from dragonbe and ibuildings tutorials)
abstract class DatabaseTestCase extends Zend_Test_PHPUnit_DatabaseTestCase
{
const DEFAULT_CONNECTION_SCHEMA = 'main';
protected $_connectionMock;
private $__configuration = NULL;
protected $_connectionSchema = self::DEFAULT_CONNECTION_SCHEMA;
protected $_seedFilesPath;
protected $dataSet;
public function __construct()
{
$this->dataSet = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet($this->getConnection());
$this->dataSet->addTable('artwork','Select* from artwork');
$this->dataSet->addTable('login','Select *from login');
$this->dataSet->addTable('category','Select *from category');
}
public function getConfiguration()
{
if ($this->__configuration == NULL) {
$this->__configuration = new Zend_Config_Ini(TEST_PATH . '/application/configs/tests.ini');
}
return $this->__configuration;
}
public function getSeedFilesPath()
{
if ($this->_seedFilesPath == NULL) {
$this->_seedFilesPath = $this->getConfiguration()->tests->seeds->folder;
}
return rtrim($this->_seedFilesPath, '/') . '/';
}
protected function getConnection()
{
if ($this->_connectionMock == NULL) {
$dbAdapterName = $this->getConfiguration()->tests->dbadapter;
$dbAdapterParams = $this->getConfiguration()->tests->dbparams->toArray();
$connection = Zend_Db::factory($dbAdapterName, $dbAdapterParams);
$this->_connectionMock = $this->createZendDbConnection(
$connection, $this->_connectionSchema
);
Zend_Db_Table_Abstract::setDefaultAdapter($connection);
}
return $this->_connectionMock;
}
protected function getDataSet()
{
return $this->createFlatXMLDataSet(TEST_PATH . '/fixtures/models/artwork-seed.xml');
}
/**
* Convert a Rowset to a Dataset
*
* #param Zend_Db_Table_Rowset_Abstract $rowset
* #param string $tableName
* #return PHPUnit_Extensions_Database_DataSet_DefaultDataSet
*/
public function convertRowsetToDataSet($rowset, $tableName = NULL)
{
$rowsetDataSet = new Zend_Test_PHPUnit_Db_DataSet_DbRowset($rowset, $tableName);
return new PHPUnit_Extensions_Database_DataSet_DefaultDataSet(array($rowsetDataSet));
}
/**
* Convert a Record to a Dataset
*
* #param array $data
* #param string $tableName
* #return PHPUnit_Extensions_Database_DataSet_DefaultDataSet
*/
public function convertRecordToDataSet(Array $data, $tableName)
{
$rowset = new Zend_Db_Table_Rowset(array('data' => array($data)));
return $this->convertRowsetToDataSet($rowset, $tableName);
}
/**
* Compare dataset with data stored in the file
*
* #param string $filename
* #param PHPUnit_Extensions_Database_DataSet_IDataSet $expected
* #return boolean
*/
public function assertDataSetsMatchXML($filename, PHPUnit_Extensions_Database_DataSet_IDataSet $actual)
{
if (empty($filename) || !is_string($filename))
throw new InvalidArgumentException(
'Second parameter "filename" is not a valid string.'
);
$expected = $this->createFlatXmlDataSet($this->getSeedFilesPath() . $filename);
return $this->assertDataSetsEqual($expected, $actual);
}
}
The answer was running parent::setUp(); inside the test class's setUp() method

Doctrine ODM (MongoDB) - Get a complete array of an object?

iv'e got a problem to receive a complete array (with all the data of the embedded childs collections and objects) of my document. My document looks exactly like this one:
use Doctrine\Common\Collections\ArrayCollection;
/** #Document(collection="user") */
class User {
/** #Id */
protected $id;
/** #String */
protected $firstname;
/** #String */
protected $lastname;
/** #EmbedMany(targetDocument="Email") */
protected $email;
/** #EmbedMany(targetDocument="Address") */
protected $address;
/** #EmbedMany(targetDocument="Subscription") */
protected $subscription;
/**
* Construct the user
*
* #param array $properties
* #throws User_Exception
*/
public function __construct(array $properties = array()) {
$this->email = new ArrayCollection();
$this->address = new ArrayCollection();
$this->subscription = new ArrayCollection();
foreach($properties as $name => $value){
$this->{$name} = $value;
}
}
...
I need a complete array of an embedded collection to output the whole data and render it by json. My query looks like this:
$query = $this->_dbContainer->getDocumentManager()->createQueryBuilder('User')->field('deletedAt')->exists(false);
$result = $query->field('id')->equals($id)->getQuery()->getSingleResult();
For example, if i call the toArray() function like this:
$array = $result->getSubscription()->toArray();
print_r($array);
Then the output ist just an array on top level:
[0] => Object Subscription...
[1] => Object Subscription...
...
How can i easily get an array like this?
[0] => array('subscriptionLabel' => 'value1', 'field' => 'value1', ...)
[1] => array('subscriptionLabel' => 'value2', 'field' => 'value2', ...)
...
Are there any best practises or maybe some missing helper scripts to prevent something ugly like this code (how to handle child -> child -> child szenarios? ugly -> ugly ugly -> ugly ugly ugly -> ...):
$example = array();
foreach($result->getSubscription() as $key => $subscription) {
$example[$key]['subscriptionLabel'] = $subscription->getSubscriptionLabel();
$example[$key]['field'] = $subscription->getField();
...
}
Thanks a lot,
Stephan
Damn simple answer! Just use ->hydrate(false) and it's done.
For find queries the results by
default are hydrated and you get
document objects back instead of
arrays. You can disable this and get
the raw results directly back from
mongo by using the hydrate(false)
method:
<?php
$users = $dm->createQueryBuilder('User')
->hydrate(false)
->getQuery()
->execute();
print_r($users);
I ran into this same need recently and solved it by creating a base class for all my entities with a toArray() function and JsonSerializable. It converts all nested references as well.
/**
* #ODM\MappedSuperclass
*/
abstract class BaseDocument implements \JsonSerializable
{
public function jsonSerialize() {
return $this->toArray();
}
public function toArray() {
$getter_names = get_class_methods(get_class($this));
$gettable_attributes = array();
foreach ($getter_names as $key => $funcName) {
if(substr($funcName, 0, 3) === 'get') {
$propName = strtolower(substr($funcName, 3, 1));
$propName .= substr($funcName, 4);
$value = $this->$funcName();
if (is_object($value) && get_class($value) == 'Doctrine\ODM\MongoDB\PersistentCollection') {
$values = array();
$collection = $value;
foreach ($collection as $obj) {
$values[] = $obj->toArray();
}
$gettable_attributes[$propName] = $values;
}
else {
$gettable_attributes[$propName] = $value;
}
}
}
return $gettable_attributes;
}
}
Now I can serialize the entity as an array or json string with json_encode($doc). Bam.
Tanks to Rooster242, you can even recursively apply toArray to embedded documents which themself extends BaseDocument by using the php is_subclass_of function :
/**
* #ODM\MappedSuperclass
*/
abstract class BaseDocument implements \JsonSerializable
{
public function jsonSerialize() {
return $this->toArray();
}
public function toArray() {
$getter_names = get_class_methods(get_class($this));
$gettable_attributes = array();
foreach ($getter_names as $key => $funcName) {
if(substr($funcName, 0, 3) === 'get') {
$propName = strtolower(substr($funcName, 3, 1));
$propName .= substr($funcName, 4);
$value = $this->$funcName();
if (is_object($value) && is_subclass_of($value,"BaseDocument")) {
$gettable_attributes[$propName] = $value->toArray();
} elseif (is_object($value) && get_class($value) == 'Doctrine\ODM\MongoDB\PersistentCollection') {
$values = array();
$collection = $value;
foreach ($collection as $obj) {
if (is_subclass_of($obj,"BaseDocument")) {
$values[] = $obj->toArray();
} else {
$values[] = $obj;
}
}
$gettable_attributes[$propName] = $values;
}
else {
$gettable_attributes[$propName] = $value;
}
}
}
return $gettable_attributes;
}
}
Just made this a bit more generic, works perfect. Just dont forget to extend it with your documents and embeds.
<?php
namespace App\Documents;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\PersistentCollection;
/**
* #ODM\MappedSuperclass
*/
abstract class BaseDocument implements \JsonSerializable
{
/**
* #return array
*/
public function jsonSerialize()
{
return $this->toArray();
}
/**
* #return array
*/
public function toArray()
{
$getterNames = get_class_methods(get_class($this));
$gettableAttributes = [];
foreach ($getterNames as $funcName) {
if (substr($funcName, 0, 3) !== 'get') {
continue;
}
$propName = strtolower(substr($funcName, 3, 1));
$propName .= substr($funcName, 4);
$value = $this->$funcName();
$gettableAttributes[$propName] = $value;
if (is_object($value)) {
if ($value instanceof PersistentCollection) {
$values = [];
$collection = $value;
foreach ($collection as $obj) {
/** #var BaseDocument $obj */
if ($obj instanceof \JsonSerializable) {
$values[] = $obj->toArray();
} else {
$values[] = $obj;
}
}
$gettableAttributes[$propName] = $values;
} elseif ($value instanceof \JsonSerializable) {
/** #var BaseDocument $value */
$gettableAttributes[$propName] = $value->toArray();
}
}
}
return $gettableAttributes;
}
}